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:
170
pro_v3.5.1/app/command/HjfVerifyAgentConfig.php
Normal file
170
pro_v3.5.1/app/command/HjfVerifyAgentConfig.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* e2e 验收:分销员等级配置检验命令
|
||||
*
|
||||
* 用法:
|
||||
* php think hjf:verify-agent-config
|
||||
*
|
||||
* 说明:
|
||||
* 验证 eb_agent_level 和 eb_agent_level_task 表中的数据配置与 PRD 3.2 一致。
|
||||
* 若不一致则输出差异明细并自动修正(--fix 参数),修正后输出最终结果。
|
||||
*
|
||||
* Class HjfVerifyAgentConfig
|
||||
* @package app\command
|
||||
*/
|
||||
class HjfVerifyAgentConfig extends Command
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName('hjf:verify-agent-config')
|
||||
->setDescription('e2e 验收分销员等级奖励积分与升级任务配置是否与 PRD 一致,传入 --fix 自动修正')
|
||||
->addOption('fix', null, \think\console\input\Option::VALUE_NONE, '自动修正不一致的配置');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output): int
|
||||
{
|
||||
$fix = (bool)$input->getOption('fix');
|
||||
$hasError = false;
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('========================================================');
|
||||
$output->writeln(' HJF 分销员等级配置 e2e 验收');
|
||||
$output->writeln('========================================================');
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 1) eb_agent_level — 奖励积分配置(PRD 3.2)
|
||||
// ----------------------------------------------------------------
|
||||
$output->writeln('');
|
||||
$output->writeln('【1】eb_agent_level 奖励积分配置');
|
||||
$output->writeln('------------------------------------------------------------');
|
||||
|
||||
$expectedLevels = [
|
||||
1 => ['name_hint' => '创客', 'direct_reward_points' => 500, 'umbrella_reward_points' => 0],
|
||||
2 => ['name_hint' => '云店', 'direct_reward_points' => 800, 'umbrella_reward_points' => 300],
|
||||
3 => ['name_hint' => '服务中心', 'direct_reward_points' => 1000, 'umbrella_reward_points' => 200],
|
||||
4 => ['name_hint' => '合伙人', 'direct_reward_points' => 1300, 'umbrella_reward_points' => 300],
|
||||
];
|
||||
|
||||
$levels = Db::name('agent_level')
|
||||
->whereIn('grade', array_keys($expectedLevels))
|
||||
->field('id,name,grade,direct_reward_points,umbrella_reward_points')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$levelsByGrade = [];
|
||||
foreach ($levels as $level) {
|
||||
$levelsByGrade[(int)$level['grade']] = $level;
|
||||
}
|
||||
|
||||
foreach ($expectedLevels as $grade => $expected) {
|
||||
if (!isset($levelsByGrade[$grade])) {
|
||||
$output->writeln(" [MISS] grade={$grade} ({$expected['name_hint']}) 行不存在!");
|
||||
$hasError = true;
|
||||
continue;
|
||||
}
|
||||
$row = $levelsByGrade[$grade];
|
||||
$errors = [];
|
||||
if ((int)$row['direct_reward_points'] !== $expected['direct_reward_points']) {
|
||||
$errors[] = "direct_reward_points={$row['direct_reward_points']}(期望 {$expected['direct_reward_points']})";
|
||||
}
|
||||
if ((int)$row['umbrella_reward_points'] !== $expected['umbrella_reward_points']) {
|
||||
$errors[] = "umbrella_reward_points={$row['umbrella_reward_points']}(期望 {$expected['umbrella_reward_points']})";
|
||||
}
|
||||
if ($errors) {
|
||||
$hasError = true;
|
||||
$output->writeln(" [FAIL] grade={$grade} {$row['name']}:" . implode(',', $errors));
|
||||
if ($fix) {
|
||||
Db::name('agent_level')->where('id', $row['id'])->update([
|
||||
'direct_reward_points' => $expected['direct_reward_points'],
|
||||
'umbrella_reward_points' => $expected['umbrella_reward_points'],
|
||||
]);
|
||||
$output->writeln(" [FIX] 已修正 grade={$grade} {$row['name']}");
|
||||
}
|
||||
} else {
|
||||
$output->writeln(" [OK] grade={$grade} {$row['name']} direct={$row['direct_reward_points']} umbrella={$row['umbrella_reward_points']}");
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 2) eb_agent_level_task — 升级任务配置(PRD 3.2)
|
||||
// ----------------------------------------------------------------
|
||||
$output->writeln('');
|
||||
$output->writeln('【2】eb_agent_level_task 升级任务配置');
|
||||
$output->writeln('------------------------------------------------------------');
|
||||
|
||||
// PRD 任务配置:[grade => [[type, number, description], ...]]
|
||||
$expectedTasks = [
|
||||
1 => [[6, 3, '直推满 3 单']],
|
||||
2 => [[7, 30, '伞下满 30 单'], [8, 3, '至少 3 个直推']],
|
||||
3 => [[7, 100, '伞下满 100 单'], [8, 3, '至少 3 个直推']],
|
||||
4 => [[7, 1000, '伞下满 1000 单'], [8, 3, '至少 3 个直推']],
|
||||
];
|
||||
|
||||
foreach ($expectedTasks as $grade => $tasks) {
|
||||
if (!isset($levelsByGrade[$grade])) {
|
||||
$output->writeln(" [SKIP] grade={$grade} 等级行不存在,跳过任务检查");
|
||||
continue;
|
||||
}
|
||||
$levelId = $levelsByGrade[$grade]['id'];
|
||||
$levelName = $levelsByGrade[$grade]['name'];
|
||||
|
||||
foreach ($tasks as [$type, $number, $desc]) {
|
||||
$taskRow = Db::name('agent_level_task')
|
||||
->where('level_id', $levelId)
|
||||
->where('type', $type)
|
||||
->field('id,number')
|
||||
->find();
|
||||
|
||||
if (!$taskRow) {
|
||||
$hasError = true;
|
||||
$output->writeln(" [MISS] grade={$grade} {$levelName} type={$type}({$desc})行不存在!");
|
||||
if ($fix) {
|
||||
Db::name('agent_level_task')->insert([
|
||||
'level_id' => $levelId,
|
||||
'type' => $type,
|
||||
'number' => $number,
|
||||
]);
|
||||
$output->writeln(" [FIX] 已插入 grade={$grade} type={$type} number={$number}");
|
||||
}
|
||||
} elseif ((int)$taskRow['number'] !== $number) {
|
||||
$hasError = true;
|
||||
$output->writeln(" [FAIL] grade={$grade} {$levelName} type={$type}({$desc})number={$taskRow['number']}(期望 {$number})");
|
||||
if ($fix) {
|
||||
Db::name('agent_level_task')->where('id', $taskRow['id'])->update(['number' => $number]);
|
||||
$output->writeln(" [FIX] 已修正 grade={$grade} type={$type} number={$number}");
|
||||
}
|
||||
} else {
|
||||
$output->writeln(" [OK] grade={$grade} {$levelName} type={$type}({$desc})number={$taskRow['number']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 输出汇总
|
||||
// ----------------------------------------------------------------
|
||||
$output->writeln('');
|
||||
$output->writeln('========================================================');
|
||||
if ($hasError) {
|
||||
if ($fix) {
|
||||
$output->writeln(' 结果:检测到配置不一致,已自动修正。');
|
||||
} else {
|
||||
$output->writeln(' 结果:检测到配置不一致,请使用 --fix 自动修正,或手动更新数据库。');
|
||||
}
|
||||
} else {
|
||||
$output->writeln(' 结果:所有配置与 PRD 一致,验收通过 ✓');
|
||||
}
|
||||
$output->writeln('========================================================');
|
||||
$output->writeln('');
|
||||
|
||||
return $hasError ? 1 : 0;
|
||||
}
|
||||
}
|
||||
@@ -39,14 +39,17 @@ class HjfAssets
|
||||
$frozenPoints = (int)($user['frozen_points'] ?? 0);
|
||||
$todayRelease = (int)floor($frozenPoints * 0.0004);
|
||||
|
||||
$availablePoints = (int)($user['available_points'] ?? 0);
|
||||
|
||||
return app('json')->successful([
|
||||
'brokerage_price' => (string)($user['brokerage_price'] ?? '0.00'),
|
||||
'now_money' => (string)($user['now_money'] ?? '0.00'),
|
||||
'frozen_points' => $frozenPoints,
|
||||
'available_points' => (int)($user['available_points'] ?? 0),
|
||||
'today_release' => $todayRelease,
|
||||
'agent_level' => (int)($user['agent_level'] ?? 0),
|
||||
'agent_level_name' => $agentLevelName,
|
||||
'brokerage_price' => (string)($user['brokerage_price'] ?? '0.00'),
|
||||
'now_money' => (string)($user['now_money'] ?? '0.00'),
|
||||
'frozen_points' => $frozenPoints,
|
||||
'available_points' => $availablePoints,
|
||||
'today_release' => $todayRelease,
|
||||
'total_points_earned' => $frozenPoints + $availablePoints,
|
||||
'agent_level' => (int)($user['agent_level'] ?? 0),
|
||||
'agent_level_name' => $agentLevelName,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class HjfOrderPayJob extends BaseJobs
|
||||
return false;
|
||||
}
|
||||
|
||||
$preUpgradeLevels = [];
|
||||
try {
|
||||
/** @var UserServices $userServices */
|
||||
$userServices = app()->make(UserServices::class);
|
||||
@@ -59,6 +60,13 @@ class HjfOrderPayJob extends BaseJobs
|
||||
}
|
||||
$uids = array_unique([$uid, $spreadUid, $twoSpreadUid]);
|
||||
|
||||
// fsgx B2:在升级前快照各上级用户的 agent_level,避免触发升级的那笔订单发放积分
|
||||
foreach ($uids as $u) {
|
||||
if ($u <= 0) continue;
|
||||
$uInfo = $userServices->get((int)$u, ['uid', 'agent_level']);
|
||||
$preUpgradeLevels[(int)$u] = $uInfo ? (int)($uInfo['agent_level'] ?? 0) : 0;
|
||||
}
|
||||
|
||||
/** @var AgentLevelServices $agentLevelServices */
|
||||
$agentLevelServices = app()->make(AgentLevelServices::class);
|
||||
$agentLevelServices->checkUserLevelFinish($uid, $uids);
|
||||
@@ -76,10 +84,30 @@ class HjfOrderPayJob extends BaseJobs
|
||||
->field('id,uid,is_queue_goods')
|
||||
->find();
|
||||
if ($orderRow) {
|
||||
// fsgx B3:计算订单中报单商品的总数量,积分按数量倍乘
|
||||
$queueQty = 1;
|
||||
try {
|
||||
$cartRows = Db::name('store_order_cart_info')
|
||||
->where('oid', (int)$orderRow['id'])
|
||||
->column('cart_info');
|
||||
$qtySum = 0;
|
||||
foreach ($cartRows as $row) {
|
||||
$item = is_string($row) ? json_decode($row, true) : $row;
|
||||
if (!empty($item['productInfo']['is_queue_goods'])) {
|
||||
$qtySum += (int)($item['cart_num'] ?? 1);
|
||||
}
|
||||
}
|
||||
if ($qtySum > 0) {
|
||||
$queueQty = $qtySum;
|
||||
}
|
||||
} catch (\Throwable $qe) {
|
||||
Log::warning("[HjfOrderPay] 计算报单商品数量异常,使用默认值1: " . $qe->getMessage());
|
||||
}
|
||||
|
||||
/** @var PointsRewardServices $pointsService */
|
||||
$pointsService = app()->make(PointsRewardServices::class);
|
||||
$pointsService->reward($uid, $orderId, (int)$orderRow['id']);
|
||||
Log::info("[HjfOrderPay] 积分奖励发放完成 uid={$uid} orderId={$orderId}");
|
||||
$pointsService->reward($uid, $orderId, (int)$orderRow['id'], $preUpgradeLevels, $queueQty);
|
||||
Log::info("[HjfOrderPay] 积分奖励发放完成 uid={$uid} orderId={$orderId} qty={$queueQty}");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("[HjfOrderPay] 积分奖励发放失败 uid={$uid} orderId={$orderId}: " . $e->getMessage());
|
||||
|
||||
@@ -175,15 +175,42 @@ class Pay implements ListenerInterface
|
||||
}
|
||||
$uids = array_filter(array_unique([$uid, $spreadUid, $twoSpreadUid]));
|
||||
|
||||
// fsgx B2:在升级前快照各上级用户的 agent_level,避免触发升级的那笔订单发放积分
|
||||
$preUpgradeLevels = [];
|
||||
foreach ($uids as $u) {
|
||||
$uInfo = $userServices->get((int)$u, ['uid', 'agent_level']);
|
||||
$preUpgradeLevels[(int)$u] = $uInfo ? (int)($uInfo['agent_level'] ?? 0) : 0;
|
||||
}
|
||||
|
||||
/** @var AgentLevelServices $agentLevelServices */
|
||||
$agentLevelServices = app()->make(AgentLevelServices::class);
|
||||
$agentLevelServices->checkUserLevelFinish($uid, array_values($uids));
|
||||
|
||||
// fsgx B3:计算订单中报单商品的总数量,积分按数量倍乘
|
||||
$queueQty = 1;
|
||||
try {
|
||||
/** @var StoreOrderCartInfoServices $cartSvc */
|
||||
$cartSvc = app()->make(StoreOrderCartInfoServices::class);
|
||||
$cartRows = $cartSvc->getColumn(['oid' => (int)$orderInfo['id']], 'cart_info');
|
||||
$qtySum = 0;
|
||||
foreach ($cartRows as $row) {
|
||||
$item = is_string($row) ? json_decode($row, true) : $row;
|
||||
if (!empty($item['productInfo']['is_queue_goods'])) {
|
||||
$qtySum += (int)($item['cart_num'] ?? 1);
|
||||
}
|
||||
}
|
||||
if ($qtySum > 0) {
|
||||
$queueQty = $qtySum;
|
||||
}
|
||||
} catch (\Throwable $qe) {
|
||||
Log::warning('[Pay] 计算报单商品数量异常,使用默认值1: ' . $qe->getMessage());
|
||||
}
|
||||
|
||||
/** @var PointsRewardServices $pointsService */
|
||||
$pointsService = app()->make(PointsRewardServices::class);
|
||||
$pointsService->reward($uid, (string)$orderInfo['order_id'], (int)$orderInfo['id']);
|
||||
$pointsService->reward($uid, (string)$orderInfo['order_id'], (int)$orderInfo['id'], $preUpgradeLevels, $queueQty);
|
||||
|
||||
Log::info('[Pay] 同步积分奖励发放完成 uid=' . $uid . ' order_id=' . $orderInfo['id']);
|
||||
Log::info('[Pay] 同步积分奖励发放完成 uid=' . $uid . ' order_id=' . $orderInfo['id'] . ' qty=' . $queueQty);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('[Pay] 同步积分奖励失败 order_id=' . $orderInfo['id'] . ': ' . $e->getMessage());
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -25,5 +25,6 @@ return [
|
||||
'holiday_gift_push_task' => \app\command\HolidayGiftPushTask::class,
|
||||
'hjf:release-points' => \app\command\HjfReleasePoints::class,
|
||||
'hjf:patch-rewards' => \app\command\HjfPatchMissingRewards::class,
|
||||
'hjf:verify-agent-config' => \app\command\HjfVerifyAgentConfig::class,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<view class="hjf-assets-page" :style="colorStyle">
|
||||
<!-- #ifdef MP -->
|
||||
<NavBar titleText="我的资产" :iconColor="iconColor" :textColor="iconColor" showBack :isScrolling="isScrolling"></NavBar>
|
||||
<!-- #endif -->
|
||||
|
||||
<view class="assets-wrapper">
|
||||
<view class="assets-header">
|
||||
@@ -116,14 +119,25 @@
|
||||
<script>
|
||||
import { getAssetsOverview } from '@/api/hjfAssets.js';
|
||||
import colors from '@/mixins/color.js';
|
||||
// #ifdef MP
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
name: 'AssetsIndex',
|
||||
|
||||
mixins: [colors],
|
||||
|
||||
components: {
|
||||
// #ifdef MP
|
||||
NavBar,
|
||||
// #endif
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
iconColor: '#FFFFFF',
|
||||
isScrolling: false,
|
||||
assetsInfo: null,
|
||||
loading: false
|
||||
};
|
||||
@@ -146,7 +160,8 @@ export default {
|
||||
},
|
||||
formattedTotalPoints() {
|
||||
if (!this.assetsInfo) return '0';
|
||||
return Number(this.assetsInfo.total_points_earned).toLocaleString();
|
||||
const val = Number(this.assetsInfo.total_points_earned);
|
||||
return isNaN(val) ? '0' : val.toLocaleString();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -160,6 +175,16 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
onPageScroll(e) {
|
||||
if (e.scrollTop > 50) {
|
||||
this.isScrolling = true;
|
||||
this.iconColor = '#333333';
|
||||
} else {
|
||||
this.isScrolling = false;
|
||||
this.iconColor = '#FFFFFF';
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchAssetsOverview() {
|
||||
this.loading = true;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<view class="brokerage-page" :style="colorStyle">
|
||||
<!-- #ifdef MP -->
|
||||
<NavBar titleText="佣金状态" :iconColor="iconColor" :textColor="iconColor" showBack :isScrolling="isScrolling"></NavBar>
|
||||
<!-- #endif -->
|
||||
<!-- 顶部渐变区域 -->
|
||||
<view class="header-gradient">
|
||||
<view class="header-gradient__circle header-gradient__circle--1"></view>
|
||||
@@ -88,14 +91,26 @@ import HjfRefundNotice from '@/components/HjfRefundNotice.vue';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
import colors from '@/mixins/color.js';
|
||||
import { spreadOrder } from '@/api/user.js';
|
||||
// #ifdef MP
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
name: 'BrokerageStatus',
|
||||
mixins: [colors],
|
||||
components: { HjfQueueProgress, HjfRefundNotice, emptyPage },
|
||||
components: {
|
||||
HjfQueueProgress,
|
||||
HjfRefundNotice,
|
||||
emptyPage,
|
||||
// #ifdef MP
|
||||
NavBar,
|
||||
// #endif
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
iconColor: '#FFFFFF',
|
||||
isScrolling: false,
|
||||
progressData: {},
|
||||
records: [],
|
||||
loading: false,
|
||||
@@ -108,6 +123,16 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
onPageScroll(e) {
|
||||
if (e.scrollTop > 50) {
|
||||
this.isScrolling = true;
|
||||
this.iconColor = '#333333';
|
||||
} else {
|
||||
this.isScrolling = false;
|
||||
this.iconColor = '#FFFFFF';
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadProgress();
|
||||
this.loadRecords();
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
:class='type == 2 ? "on" : ""'
|
||||
@click='changeType(2)'
|
||||
>储值</view>
|
||||
<view
|
||||
class='item'
|
||||
:class='type == "queue_refund" ? "on" : ""'
|
||||
@click='changeType("queue_refund")'
|
||||
>公排退款</view>
|
||||
</view>
|
||||
|
||||
<!-- 账单列表 -->
|
||||
@@ -37,14 +32,9 @@
|
||||
:key="indexn"
|
||||
>
|
||||
<view>
|
||||
<view class='name line1'>
|
||||
{{ vo.title }}
|
||||
<!-- 公排退款标记 -->
|
||||
<text
|
||||
v-if="vo.type === 'queue_refund'"
|
||||
class='queue-refund-tag'
|
||||
>公排退款</text>
|
||||
</view>
|
||||
<view class='name line1'>
|
||||
{{ vo.title }}
|
||||
</view>
|
||||
<view class='time-text'>{{ vo.add_time }}</view>
|
||||
</view>
|
||||
<view class='num' :class="vo.pm ? 'num-add' : 'num-sub'">
|
||||
@@ -86,7 +76,6 @@
|
||||
* - 0: 全部
|
||||
* - 1: 消费
|
||||
* - 2: 储值
|
||||
* - "queue_refund": 公排退款(type=queue_refund 时显示专属标记)
|
||||
*
|
||||
* 列表按日期分组,支持上拉分页加载。
|
||||
*
|
||||
@@ -109,11 +98,11 @@
|
||||
page: 1,
|
||||
/** @type {number} 每页条数 */
|
||||
limit: 15,
|
||||
/**
|
||||
* 当前筛选类型
|
||||
* 0=全部 1=消费 2=储值 "queue_refund"=公排退款
|
||||
* @type {number|string}
|
||||
*/
|
||||
/**
|
||||
* 当前筛选类型
|
||||
* 0=全部 1=消费 2=储值
|
||||
* @type {number|string}
|
||||
*/
|
||||
type: 0,
|
||||
/** @type {Array<Object>} 按日期分组的账单列表 */
|
||||
userBillList: [],
|
||||
@@ -201,12 +190,12 @@
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换账单筛选类型并重置列表
|
||||
*
|
||||
* @param {number|string} type - 目标类型(0全部 1消费 2储值 "queue_refund"公排退款)
|
||||
* @returns {void}
|
||||
*/
|
||||
/**
|
||||
* 切换账单筛选类型并重置列表
|
||||
*
|
||||
* @param {number|string} type - 目标类型(0全部 1消费 2储值)
|
||||
* @returns {void}
|
||||
*/
|
||||
changeType(type) {
|
||||
if (this.type === type) return;
|
||||
this.type = type;
|
||||
@@ -267,19 +256,6 @@
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 公排退款标记 */
|
||||
.queue-refund-tag {
|
||||
display: inline-block;
|
||||
margin-left: 10rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
background: var(--view-theme);
|
||||
vertical-align: middle;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 顶部类型筛选导航 */
|
||||
.bill-details .nav {
|
||||
background-color: #fff;
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
<w-barcode :options="config.bar"></w-barcode>
|
||||
</view> -->
|
||||
<view class="acea-row row-center-wrapper" style="margin-top: 56rpx;">
|
||||
<!-- #ifdef MP -->
|
||||
<image :src="qrc" class="qrcode"></image>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<image v-if="qrc" :src="qrc" class="qrcode"></image>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<image v-if="$wechat.isWeixin()" :src="qrc" class="qrcode"></image>
|
||||
<w-qrcode v-else :options="config.qrc"></w-qrcode>
|
||||
@@ -132,14 +132,14 @@
|
||||
routineUrl,
|
||||
wechatUrl
|
||||
} = res.data;
|
||||
// #ifdef MP
|
||||
this.qrc = routineUrl;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if (this.$wechat.isWeixin()) {
|
||||
this.qrc = wechatUrl;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
this.qrc = routineUrl || '';
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if (this.$wechat.isWeixin()) {
|
||||
this.qrc = wechatUrl || '';
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
},
|
||||
goDetail(val) {
|
||||
|
||||
@@ -205,22 +205,23 @@
|
||||
if (url.indexOf('https://') > -1) return url;
|
||||
else return url.replace('http://', 'https://');
|
||||
},
|
||||
//获取图片
|
||||
async spreadMsg() {
|
||||
let res = await spreadMsg()
|
||||
this.spreadData = res.data.spread
|
||||
this.nickName = res.data.nickname
|
||||
this.siteName = res.data.site_name
|
||||
uni.showLoading({
|
||||
title: '海报生成中',
|
||||
mask: true
|
||||
});
|
||||
//获取图片
|
||||
async spreadMsg() {
|
||||
let res = await spreadMsg()
|
||||
this.spreadData = res.data.spread
|
||||
this.nickName = res.data.nickname
|
||||
this.siteName = res.data.site_name
|
||||
uni.showLoading({
|
||||
title: '海报生成中',
|
||||
mask: true
|
||||
});
|
||||
try {
|
||||
for (let i = 0; i < res.data.spread.length; i++) {
|
||||
let that = this
|
||||
let arr2 = [];
|
||||
let img = await this.downloadFilestoreImage(res.data.spread[i].pic);
|
||||
let avatar = await this.downloadFilestoreImage(res.data.avatar);
|
||||
let followCode = res.data.qrcode?await this.downloadFilestoreImage(res.data.qrcode):'';
|
||||
let followCode = res.data.qrcode ? await this.downloadFilestoreImage(res.data.qrcode) : '';
|
||||
// #ifdef H5
|
||||
arr2 = [followCode || this.codeSrc, img, avatar]
|
||||
// #endif
|
||||
@@ -231,8 +232,12 @@
|
||||
// #ifdef APP-PLUS
|
||||
arr2 = [this.codeSrc, img, avatar]
|
||||
// #endif
|
||||
if (!img) {
|
||||
console.warn('[海报] 背景图下载失败,跳过第', i, '张海报生成');
|
||||
continue;
|
||||
}
|
||||
this.$nextTick(function(){
|
||||
that.$util.userPosterCanvas(arr2, res.data.nickname, res.data.site_name, i, this.wd, this.hg, (
|
||||
that.$util.userPosterCanvas(arr2, res.data.nickname, res.data.site_name, i, that.wd, that.hg, (
|
||||
tempFilePath) => {
|
||||
that.$set(that.posterImage, i, tempFilePath);
|
||||
// #ifdef MP
|
||||
@@ -245,8 +250,12 @@
|
||||
});
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[海报] 生成异常:', e);
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
},
|
||||
}
|
||||
},
|
||||
// #ifdef MP
|
||||
async routineCode() {
|
||||
let res = await routineCode()
|
||||
@@ -350,23 +359,26 @@
|
||||
});
|
||||
},
|
||||
// #endif
|
||||
//图片转符合安全域名路径
|
||||
downloadFilestoreImage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let that = this;
|
||||
uni.downloadFile({
|
||||
url: that.setDomain(url),
|
||||
success: function(res) {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: function() {
|
||||
return that.$util.Tips({
|
||||
title: ''
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
//图片转符合安全域名路径
|
||||
downloadFilestoreImage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let that = this;
|
||||
if (!url) {
|
||||
resolve('');
|
||||
return;
|
||||
}
|
||||
uni.downloadFile({
|
||||
url: that.setDomain(url),
|
||||
success: function(res) {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: function(err) {
|
||||
console.error('[海报] 图片下载失败:', url, err);
|
||||
resolve('');
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
setShareInfoStatus: function() {
|
||||
if (this.$wechat.isWeixin()) {
|
||||
if (this.isLogin) {
|
||||
|
||||
Reference in New Issue
Block a user