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:
panchengyong
2026-03-28 10:23:20 +08:00
parent 8d109cbc01
commit ec56ae3286
13 changed files with 437 additions and 126 deletions

View File

@@ -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) {