修复(收益): 纠正累计收益统计口径为真实分成收益,排除dmepa与兑换券释放干扰

这个提交包含在:
chen
2026-09-19 16:37:54 +08:00
父节点 cbac2a69f4
当前提交 bee5288e88
共有 8 个文件被更改,包括 141 次插入 和 4 次删除

查看文件

@@ -402,12 +402,15 @@ class Agent extends Commission
// 2. 团队成员为目标用户贡献的累计收益与本月收益
$monthStart = strtotime(date('Y-m-01 00:00:00'));
$cashTypes = \addons\shopro\service\partner\PartnerConfig::cashTypes();
$teamTotalReward = RewardModel::where('user_id', $targetUserId)
->whereIn('buyer_id', $teamUserIds)
->whereIn('reward_type', $cashTypes)
->where('status', 1)
->sum('amount');
$teamMonthReward = RewardModel::where('user_id', $targetUserId)
->whereIn('buyer_id', $teamUserIds)
->whereIn('reward_type', $cashTypes)
->where('status', 1)
->where('createtime', '>=', $monthStart)
->sum('amount');

查看文件

@@ -85,7 +85,9 @@ class User extends Common
$agent = \addons\shopro\service\partner\IdentityService::findAgent($user->id);
$partner = \addons\shopro\service\partner\IdentityService::attachProfile($agent, null, $user);
if (empty($partner['total_income']) || $partner['total_income'] === '0.00') {
$cashTypes = \addons\shopro\service\partner\PartnerConfig::cashTypes();
$userRewardCash = \app\admin\model\shopro\partner\Reward::where('user_id', $user->id)
->whereIn('reward_type', $cashTypes)
->where('status', 1)
->where('amount', '>', 0)
->sum('amount');

查看文件

@@ -135,7 +135,7 @@ class IdentityService
public static function formatMadow($value)
{
$floatVal = (float) $value;
// 当存在小于 1 豆且大于 0 的微量dmepa时,自适应保留有效小数(最多4位),避免直接抹零显示为 0 豆
// 当存在小于 1 枚且大于 0 的微量dmepa时,自适应保留有效小数(最多4位),避免直接抹零显示为 0 枚
if ($floatVal > 0 && $floatVal < 1) {
return rtrim(rtrim(number_format($floatVal, 4, '.', ''), '0'), '.');
}
@@ -205,7 +205,9 @@ class IdentityService
$data = is_object($agent) ? $agent->toArray() : $agent;
$userId = intval($data['user_id'] ?? 0);
$agentTotalIncome = floatval($data['total_income'] ?? 0);
$cashTypes = PartnerConfig::cashTypes();
$rewardCash = $userId > 0 ? \app\admin\model\shopro\partner\Reward::where('user_id', $userId)
->whereIn('reward_type', $cashTypes)
->where('status', 1)
->where('amount', '>', 0)
->sum('amount') : 0;

查看文件

@@ -305,6 +305,14 @@ class PartnerConfig
];
}
/**
* 真正的分成现金奖励类型(排除 dmepa 和 兑换券)
*/
public static function cashTypes()
{
return array_merge(self::instantTypes(), ['venue_share', 'county_share']);
}
public static function defaultRules()
{
return [

查看文件

@@ -109,9 +109,10 @@ class Dashboard extends Common
$todayNetGmv = max(0, round($todayGmv - $todayRefundAmount, 2));
$refundRate = $totalGmv > 0 ? round(($totalRefundAmount / $totalGmv) * 100, 2) : 0.00;
// 现金分成总支出 (status = 1 真实入账)
$totalPayout = (float) Reward::where('status', 1)->sum('amount');
$todayPayout = (float) Reward::where('status', 1)->whereTime('createtime', 'today')->sum('amount');
// 现金分成总支出 (status = 1 真实入账,排除 dmepa 及兑换券)
$cashTypes = \addons\shopro\service\partner\PartnerConfig::cashTypes();
$totalPayout = (float) Reward::where('status', 1)->whereIn('reward_type', $cashTypes)->sum('amount');
$todayPayout = (float) Reward::where('status', 1)->whereIn('reward_type', $cashTypes)->whereTime('createtime', 'today')->sum('amount');
// 平台有效留存 = 有效净 GMV (扣除退款后) - 真实分成总支出
$netRetention = round($netGmv - $totalPayout, 2);

查看文件

@@ -78,7 +78,9 @@ class Agent extends Common
: ('已暂停' . ($ago !== null ? '(距今 ' . $ago . ' 天)' : ''));
}
$cashTypes = \addons\shopro\service\partner\PartnerConfig::cashTypes();
$totalCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
->whereIn('reward_type', $cashTypes)
->where('status', 1)
->where('amount', '>', 0)
->sum('amount');
@@ -88,6 +90,7 @@ class Agent extends Common
->where('reward_type', 'push3')
->sum('amount');
$partnerExclusiveCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
->whereIn('reward_type', $cashTypes)
->where('status', 1)
->where('amount', '>', 0)
->where('reward_type', '<>', 'push3')

查看文件

@@ -144,7 +144,9 @@ class User extends Common
// 累计现金收益统计(与 IdentityService 严格对齐,兼顾历史分销与收益中心)
$agentTotalIncome = floatval($agent->total_income ?? 0);
$cashTypes = \addons\shopro\service\partner\PartnerConfig::cashTypes();
$totalCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
->whereIn('reward_type', $cashTypes)
->where('status', 1)
->where('amount', '>', 0)
->sum('amount');
@@ -156,6 +158,7 @@ class User extends Common
->where('reward_type', 'push3')
->sum('amount');
$partnerExclusiveCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
->whereIn('reward_type', $cashTypes)
->where('status', 1)
->where('amount', '>', 0)
->where('reward_type', '<>', 'push3')

查看文件

@@ -0,0 +1,115 @@
const { execSync } = require('child_process');
let passed = 0;
let failed = 0;
function assert(condition, testName, detail = '') {
if (condition) {
console.log(` [PASS] ${testName}`);
passed++;
} else {
console.error(` [FAIL] ${testName} - ${detail}`);
failed++;
}
}
console.log("========================================================");
console.log(" 累计收益口径修正测试:严格限定为真实分成收益,排除dmepa与兑换券");
console.log("========================================================\n");
// 1. 本地代码与定义合规检查
console.log("一、代码与规则定义检查:");
const fs = require('fs');
const path = require('path');
const partnerConfigPath = path.resolve(__dirname, '../../addons/shopro/service/partner/PartnerConfig.php');
const configContent = fs.readFileSync(partnerConfigPath, 'utf-8');
assert(configContent.includes('public static function cashTypes()'), 'PartnerConfig 包含统一现金分成类型定义 cashTypes()');
assert(configContent.includes('array_merge(self::instantTypes(), [\'venue_share\', \'county_share\'])'), 'cashTypes 包含即时奖与日结分红共10种现金分成类型');
const identityServicePath = path.resolve(__dirname, '../../addons/shopro/service/partner/IdentityService.php');
const identityContent = fs.readFileSync(identityServicePath, 'utf-8');
assert(identityContent.includes('whereIn(\'reward_type\', $cashTypes)'), 'IdentityService::attachAgent 严格通过 cashTypes 过滤现金分成');
const userControllerPath = path.resolve(__dirname, '../../addons/shopro/controller/user/User.php');
const userControllerContent = fs.readFileSync(userControllerPath, 'utf-8');
assert(userControllerContent.includes('whereIn(\'reward_type\', $cashTypes)'), 'User.php profile 接口严格通过 cashTypes 过滤现金分成');
// 2. 线上接口模拟测试(使用真实用户数据进行场景验证)
console.log("\n二、线上用户接口场景模拟测试:");
try {
const remoteScript = `#!/bin/bash
php -r '
define("APP_PATH", "/www/wwwroot/henglian.gynhc.com/application/");
require "/www/wwwroot/henglian.gynhc.com/thinkphp/base.php";
\\think\\App::initCommon();
\$auth = \\app\\common\\library\\Auth::instance();
\$auth->direct(10);
file_put_contents("/tmp/user10_token.txt", \$auth->getToken());
\$auth->direct(20);
file_put_contents("/tmp/user20_token.txt", \$auth->getToken());
'
TOKEN10=\$(cat /tmp/user10_token.txt)
TOKEN20=\$(cat /tmp/user20_token.txt)
curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H "token: \$TOKEN10" -H "platform: WechatMiniProgram" "https://henglian.gynhc.com/addons/shopro/commission.agent" > /tmp/test_user10_agent.json
curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H "token: \$TOKEN10" -H "platform: WechatMiniProgram" "https://henglian.gynhc.com/addons/shopro/user.user/profile" > /tmp/test_user10_profile.json
curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H "token: \$TOKEN10" -H "platform: WechatMiniProgram" "https://henglian.gynhc.com/addons/shopro/partner.index/index" > /tmp/test_user10_index.json
curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H "token: \$TOKEN20" -H "platform: WechatMiniProgram" "https://henglian.gynhc.com/addons/shopro/commission.agent" > /tmp/test_user20_agent.json
curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H "token: \$TOKEN20" -H "platform: WechatMiniProgram" "https://henglian.gynhc.com/addons/shopro/partner.index/rewards" > /tmp/test_user20_rewards.json
curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H "platform: WechatMiniProgram" "https://henglian.gynhc.com/addons/shopro/commission.agent" > /tmp/test_unauth.json
`;
fs.writeFileSync('/tmp/test_total_income_runner.sh', remoteScript);
execSync(`sshpass -p 'Bjsslxz1' scp -o StrictHostKeyChecking=no /tmp/test_total_income_runner.sh root@118.184.172.94:/tmp/runner.sh`);
execSync(`sshpass -p 'Bjsslxz1' ssh -o StrictHostKeyChecking=no root@118.184.172.94 "bash /tmp/runner.sh"`);
const fetchCmd = `sshpass -p 'Bjsslxz1' ssh -o StrictHostKeyChecking=no root@118.184.172.94 "cat /tmp/test_user10_agent.json && echo '===SEP===' && cat /tmp/test_user10_profile.json && echo '===SEP===' && cat /tmp/test_user10_index.json && echo '===SEP===' && cat /tmp/test_user20_agent.json && echo '===SEP===' && cat /tmp/test_user20_rewards.json && echo '===SEP===' && cat /tmp/test_unauth.json"`;
const fetched = execSync(fetchCmd, { timeout: 25000 }).toString();
const parts = fetched.split('===SEP===').map(p => p.trim());
const u10Agent = JSON.parse(parts[0]);
const u10Profile = JSON.parse(parts[1]);
const u10Index = JSON.parse(parts[2]);
const u20Agent = JSON.parse(parts[3]);
const u20Rewards = JSON.parse(parts[4]);
const unauth = JSON.parse(parts[5]);
// 场景 1:用户10(尚善,仅有自购订单并释放兑换券,无下级分成收益)
console.log("\n场景 1:用户10(仅自购与兑换券释放,无分成收益):");
assert(u10Agent.code === 1, 'commission.agent 返回成功');
assert(u10Agent.data.total_income === '0.00', `累计收益严格为分成收益 (0.00),实际: ${u10Agent.data.total_income}`);
assert(Number(u10Agent.data.voucher_available) > 0, `可用兑换券保持独立正常发放 (${u10Agent.data.voucher_available})`);
assert(u10Agent.data.total_income !== u10Agent.data.voucher_available, '累计收益不再与兑换券数值混淆');
assert(u10Profile.data.total_income === '0.00', `user.user/profile 累计收益同步为 0.00`);
assert(u10Index.data.total_income === '0.00', `partner.index/index 累计收益同步为 0.00`);
// 场景 2:用户20(有真实团队分成和日结现金分红)
console.log("\n场景 2:用户20(有真实团队分成与日结分红):");
assert(u20Agent.code === 1, 'user20 commission.agent 返回成功');
const u20Income = Number(u20Agent.data.total_income);
assert(u20Income > 0, `user20 累计收益正确统计真实分成收益 (>0),实际: ${u20Agent.data.total_income}`);
assert(u20Rewards.code === 1, 'user20 partner.index/rewards 列表接口成功');
assert(u20Rewards.data.total_income === u20Agent.data.total_income, 'rewards 接口累计收益与 agent 接口严格一致');
assert(u20Rewards.data.data.length > 0, `rewards 明细包含 ${u20Rewards.data.data.length} 笔真实分成收益`);
assert(u20Rewards.data.data.every(r => !['voucher_release', 'voucher_grant', 'madow_share'].includes(r.reward_type)),
'收益明细列表中绝无兑换券释放或dmepa记录混入');
// 场景 3:未登录拦截与安全防御
console.log("\n场景 3:未登录参数校验拦截测试:");
assert(unauth.code === 401, '未传 token 严格返回 401 认证拦截');
assert(unauth.msg === '请登录后操作', '返回中文友好提示“请登录后操作”');
} catch (e) {
console.error("测试执行异常:", e.message);
failed++;
}
console.log("\n========================================================");
console.log(`测试结果: 通过 ${passed} 项, 失败 ${failed} 项`);
console.log("========================================================");
if (failed > 0) {
process.exit(1);
}