299 行
14 KiB
PHP
299 行
14 KiB
PHP
<?php
|
||
|
||
namespace tests\partner;
|
||
|
||
// 引导 ThinkPHP 环境
|
||
define('APP_PATH', __DIR__ . '/../../application/');
|
||
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
|
||
require_once __DIR__ . '/../../thinkphp/base.php';
|
||
require_once __DIR__ . '/../../thinkphp/helper.php';
|
||
require_once __DIR__ . '/../../application/common.php';
|
||
|
||
\think\Config::set(include APP_PATH . 'database.php', 'database');
|
||
\think\Loader::addNamespace('app', APP_PATH);
|
||
require_once __DIR__ . '/../../addons/shopro/helper/helper.php';
|
||
|
||
use app\admin\controller\shopro\Dashboard;
|
||
use think\Request;
|
||
|
||
class TestableDashboard extends Dashboard
|
||
{
|
||
public $lastOutput = null;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->request = Request::instance();
|
||
}
|
||
|
||
public function success($msg = '', $url = null, $data = '', $wait = 3, array $header = [])
|
||
{
|
||
$this->lastOutput = [
|
||
'code' => 1,
|
||
'msg' => $msg,
|
||
'data' => $data
|
||
];
|
||
}
|
||
|
||
public function setGetParams(array $params)
|
||
{
|
||
$this->request->get($params);
|
||
}
|
||
|
||
public function error($msg = '', $url = null, $data = '', $wait = 3, array $header = [])
|
||
{
|
||
$this->lastOutput = [
|
||
'code' => 0,
|
||
'msg' => $msg,
|
||
'data' => $data
|
||
];
|
||
}
|
||
}
|
||
|
||
class DashboardWealthTest
|
||
{
|
||
private $passed = 0;
|
||
private $failed = 0;
|
||
|
||
public function run()
|
||
{
|
||
echo "========================================================\n";
|
||
echo " 仪表盘专业财富观与分成透视专项测试 (Dashboard Wealth) \n";
|
||
echo "========================================================\n\n";
|
||
|
||
$this->testTotalWealthData();
|
||
$this->testAllRolesStatisticsIntegrity();
|
||
$this->testChartNormalData();
|
||
$this->testChartEmptyDateFallback();
|
||
$this->testRankingPartnerAndDailyData();
|
||
$this->testWealthFinancialFormulaIntegrity();
|
||
$this->testCfoAndCooOverview();
|
||
|
||
echo "\n--------------------------------------------------------\n";
|
||
echo "测试执行结果: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n";
|
||
echo "========================================================\n";
|
||
return $this->failed === 0 ? 0 : 1;
|
||
}
|
||
|
||
private function assert($condition, $testName, $detail = '')
|
||
{
|
||
if ($condition) {
|
||
echo " [PASS] {$testName}\n";
|
||
$this->passed++;
|
||
} else {
|
||
echo " [FAIL] {$testName} - {$detail}\n";
|
||
$this->failed++;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 测试 1:全局总览 total() 财富指标与资产池完整性
|
||
*/
|
||
public function testTotalWealthData()
|
||
{
|
||
$controller = new TestableDashboard();
|
||
$controller->total();
|
||
|
||
$data = $controller->lastOutput['data'] ?? [];
|
||
$wealth = $data['wealth_data'] ?? null;
|
||
$madow = $data['madow_data'] ?? null;
|
||
$voucher = $data['voucher_data'] ?? null;
|
||
$network = $data['partner_network'] ?? null;
|
||
|
||
$this->assert(!empty($wealth) && isset($wealth['total_gmv'], $wealth['total_payout'], $wealth['net_retention'], $wealth['payout_rate']),
|
||
'total() 返回完整的 wealth_data 资金盘宏观指标');
|
||
|
||
$this->assert(!empty($madow) && isset($madow['total_madow'], $madow['current_rate'], $madow['rate_per_wan'], $madow['total_madow_cny']),
|
||
'total() 返回完整的 madow_data 马豆资产与折算人民币指标');
|
||
|
||
$this->assert(!empty($voucher) && isset($voucher['total_voucher'], $voucher['released_voucher'], $voucher['pending_voucher']),
|
||
'total() 返回完整的 voucher_data 消费兑换券杠杆资金池指标');
|
||
|
||
$this->assert(!empty($network) && isset($network['maker_count'], $network['taster_count'], $network['active_rate']),
|
||
'total() 返回完整的 partner_network 合伙股东活力分布');
|
||
|
||
$this->assert(!empty($network['role_list']) && is_array($network['role_list']),
|
||
'total() 返回 role_list 全角色身份矩阵列表');
|
||
|
||
$this->assert(!empty($network['role_stats']) && is_array($network['role_stats']),
|
||
'total() 返回 role_stats 全角色身份字典索引');
|
||
}
|
||
|
||
/**
|
||
* 测试 2:每一个角色身份的会员数、马豆数量、分佣数量与马豆价值精算校验
|
||
*/
|
||
public function testAllRolesStatisticsIntegrity()
|
||
{
|
||
$controller = new TestableDashboard();
|
||
$controller->total();
|
||
|
||
$roleStats = $controller->lastOutput['data']['partner_network']['role_stats'] ?? [];
|
||
$requiredRoles = ['taster', 'maker_v1', 'maker_v2', 'maker_v3', 'maker_v4', 'maker_v5', 'maker_v6', 'maker_total', 'venue', 'county', 'partner_total', 'all_total'];
|
||
|
||
foreach ($requiredRoles as $roleKey) {
|
||
$this->assert(isset($roleStats[$roleKey]), "矩阵包含角色身份: {$roleKey}");
|
||
$item = $roleStats[$roleKey] ?? [];
|
||
$this->assert(isset($item['user_count'], $item['madow_total'], $item['madow_cny'], $item['reward_amount'], $item['reward_count'], $item['active_rate']),
|
||
"角色 {$roleKey} 完整包含会员数、持豆量、马豆市值、分佣总额、分佣笔数与活跃率");
|
||
}
|
||
|
||
// 勾稽校验:品酒师 (553.75) + 创客队伍 (838.00) = 全网分佣 (1391.75)
|
||
$tasterReward = (float)($roleStats['taster']['reward_amount'] ?? 0);
|
||
$makerReward = (float)($roleStats['maker_total']['reward_amount'] ?? 0);
|
||
$allReward = (float)($roleStats['all_total']['reward_amount'] ?? 0);
|
||
$this->assert(abs(($tasterReward + $makerReward) - $allReward) < 0.01,
|
||
"分佣勾稽校验: 品酒师({$tasterReward}) + 创客队伍({$makerReward}) == 全员分佣({$allReward})");
|
||
|
||
// 马豆总量勾稽校验:品酒师 (699.0) + 创客队伍 (699.0) = 全网马豆 (1398.0)
|
||
$tasterMadow = (float)($roleStats['taster']['madow_total'] ?? 0);
|
||
$makerMadow = (float)($roleStats['maker_total']['madow_total'] ?? 0);
|
||
$allMadow = (float)($roleStats['all_total']['madow_total'] ?? 0);
|
||
$this->assert(abs(($tasterMadow + $makerMadow) - $allMadow) < 0.0001,
|
||
"马豆勾稽校验: 品酒师({$tasterMadow}) + 创客队伍({$makerMadow}) == 全网马豆({$allMadow})");
|
||
}
|
||
|
||
/**
|
||
* 测试 3:核心走势 chart() 正常日期区间返回
|
||
*/
|
||
public function testChartNormalData()
|
||
{
|
||
$controller = new TestableDashboard();
|
||
$start = date('Y-m-d 00:00:00', strtotime('-7 days'));
|
||
$end = date('Y-m-d 23:59:59');
|
||
$controller->setGetParams(['date' => "{$start} - {$end}"]);
|
||
|
||
$controller->chart();
|
||
$data = $controller->lastOutput['data'] ?? [];
|
||
|
||
$this->assert(isset($data['payAmountNum'], $data['payoutNum'], $data['retentionNum'], $data['payoutRate']),
|
||
'chart() 正常区间包含 payAmountNum, payoutNum, retentionNum, payoutRate 复合指标');
|
||
|
||
$this->assert(isset($data['splitStructure']) && is_array($data['splitStructure']) && count($data['splitStructure']) === 4,
|
||
'chart() splitStructure 包含四大板块(直推、创客、渠道、均分)');
|
||
|
||
$this->assert(isset($data['rewardDetails']) && is_array($data['rewardDetails']),
|
||
'chart() rewardDetails 包含收益明细穿透统计');
|
||
}
|
||
|
||
/**
|
||
* 测试 3:边界条件 chart() 空日期/异常传参自动防御
|
||
*/
|
||
public function testChartEmptyDateFallback()
|
||
{
|
||
$controller = new TestableDashboard();
|
||
$controller->setGetParams(['date' => '']);
|
||
$controller->chart();
|
||
$data = $controller->lastOutput['data'] ?? [];
|
||
|
||
$this->assert(!empty($data) && isset($data['payAmountNum']),
|
||
'chart() 传入空日期时自动降级为当日时间范围并安全返回,不抛异常');
|
||
}
|
||
|
||
/**
|
||
* 测试 4:排行与对账 ranking() 合伙股东收益排行与最新日结
|
||
*/
|
||
public function testRankingPartnerAndDailyData()
|
||
{
|
||
$controller = new TestableDashboard();
|
||
$controller->ranking();
|
||
$data = $controller->lastOutput['data'] ?? [];
|
||
|
||
$partnerRanking = $data['partner_ranking'] ?? null;
|
||
$recentReport = $data['daily_report_recent'] ?? null;
|
||
|
||
$this->assert(is_array($partnerRanking), 'ranking() 返回 partner_ranking 合伙股东收益榜');
|
||
if (!empty($partnerRanking)) {
|
||
$top1 = $partnerRanking[0];
|
||
$this->assert(isset($top1['user_id'], $top1['nickname'], $top1['total_income'], $top1['identity_text']),
|
||
'partner_ranking 包含用户ID、昵称、收益金额与身份等级标签');
|
||
}
|
||
|
||
$this->assert($recentReport === null || isset($recentReport['settle_date'], $recentReport['gmv'], $recentReport['rate']),
|
||
'ranking() 返回 daily_report_recent 最新日结对账概览');
|
||
}
|
||
|
||
/**
|
||
* 测试 5:财务公式精算勾稽校验
|
||
*/
|
||
public function testWealthFinancialFormulaIntegrity()
|
||
{
|
||
$controller = new TestableDashboard();
|
||
$controller->total();
|
||
$wealth = $controller->lastOutput['data']['wealth_data'] ?? [];
|
||
|
||
$gmv = (float)($wealth['total_gmv'] ?? 0);
|
||
$payout = (float)($wealth['total_payout'] ?? 0);
|
||
$retention = (float)($wealth['net_retention'] ?? 0);
|
||
$payoutRate = (float)($wealth['payout_rate'] ?? 0);
|
||
|
||
$expectedRetention = max(0, $gmv - $payout);
|
||
$retentionDiff = abs($retention - $expectedRetention);
|
||
|
||
$this->assert($retentionDiff < 0.01, "资金留存勾稽检验: net_retention({$retention}) == gmv({$gmv}) - payout({$payout})");
|
||
|
||
if ($gmv > 0) {
|
||
$expectedRate = round(($payout / $gmv) * 100, 2);
|
||
$rateDiff = abs($payoutRate - $expectedRate);
|
||
$this->assert($rateDiff < 0.1, "全网分成拨比率精算检验: payout_rate({$payoutRate}%) == (payout/gmv)*100%");
|
||
} else {
|
||
$this->assert($payoutRate == 0, "GMV 为 0 时分成拨比率恒为 0%");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 测试 7:CFO 财务中枢与 COO 运营全貌数据结构与勾稽
|
||
*/
|
||
public function testCfoAndCooOverview()
|
||
{
|
||
$controller = new TestableDashboard();
|
||
$controller->total();
|
||
|
||
$data = $controller->lastOutput['data'] ?? [];
|
||
$finance = $data['finance_overview'] ?? null;
|
||
$operation = $data['operation_overview'] ?? null;
|
||
|
||
$this->assert($finance !== null, "total() 返回完整的 finance_overview 财务全貌中枢数据");
|
||
$this->assert($operation !== null, "total() 返回完整的 operation_overview 运营全貌中枢数据");
|
||
|
||
if ($finance) {
|
||
$this->assert(isset($finance['commission_float']), "财务包含 commission_float 未提现佣金沉淀");
|
||
$this->assert(isset($finance['total_float_funds']), "财务包含 total_float_funds 免息流动沉淀池");
|
||
$this->assert(isset($finance['total_withdraw_paid']), "财务包含 total_withdraw_paid 累计已打款出款");
|
||
$this->assert(isset($finance['pending_withdraw_amount']), "财务包含 pending_withdraw_amount 待审核出款风控资金");
|
||
$this->assert(isset($finance['real_net_cash_flow']), "财务包含 real_net_cash_flow 真实净现金流");
|
||
$this->assert(isset($finance['total_future_liability']), "财务包含 total_future_liability 未来承兑准备金");
|
||
|
||
$floatDiff = abs((float)$finance['total_float_funds'] - ((float)$finance['commission_float'] + (float)$finance['user_balance_float']));
|
||
$this->assert($floatDiff < 0.01, "资金浮存勾稽: total_float_funds == commission_float + user_balance_float");
|
||
|
||
$liabilityDiff = abs((float)$finance['total_future_liability'] - ((float)$finance['pending_voucher_liability'] + (float)$finance['madow_market_liability'] + (float)$finance['commission_liability']));
|
||
$this->assert($liabilityDiff < 0.01, "承兑负债勾稽: total_future_liability == pending_voucher + madow_market + commission");
|
||
}
|
||
|
||
if ($operation) {
|
||
$this->assert(isset($operation['total_registered']), "运营包含 total_registered 注册用户数");
|
||
$this->assert(isset($operation['paid_users_count']), "运营包含 paid_users_count 付费买家数");
|
||
$this->assert(isset($operation['pay_conversion_rate']), "运营包含 pay_conversion_rate 付费转化率");
|
||
$this->assert(isset($operation['repeat_purchase_rate']), "运营包含 repeat_purchase_rate 复购率");
|
||
$this->assert(isset($operation['arpu']), "运营包含 arpu 付费买家客单贡献");
|
||
$this->assert(isset($operation['active_partners']), "运营包含 active_partners 活跃合伙人");
|
||
$this->assert(isset($operation['warning_partners']), "运营包含 warning_partners 7天内即将断档合伙人");
|
||
$this->assert(isset($operation['frozen_partners']), "运营包含 frozen_partners 已断档冻结合伙人");
|
||
$this->assert(is_array($operation['warning_list']), "运营包含 warning_list 即将断档人员名单");
|
||
$this->assert(isset($operation['low_stock_count']), "运营包含 low_stock_count 低库存告急SKU数量");
|
||
$this->assert(is_array($operation['low_stock_list']), "运营包含 low_stock_list 低库存SKU商品列表");
|
||
$this->assert(isset($operation['health']['score']), "运营包含经营健康度评分 score");
|
||
$this->assert(isset($operation['health']['grade']), "运营包含经营健康度评级 grade");
|
||
|
||
$this->assert(
|
||
$operation['paid_users_count'] == ($operation['single_buy_users_count'] + $operation['repeat_buy_users_count']),
|
||
"用户转化勾稽: paid_users_count({$operation['paid_users_count']}) == single({$operation['single_buy_users_count']}) + repeat({$operation['repeat_buy_users_count']})"
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (php_sapi_name() === 'cli' && basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME'])) {
|
||
$tester = new DashboardWealthTest();
|
||
exit($tester->run());
|
||
}
|