load(); \support\App::loadAllConfig(['route']); date_default_timezone_set('Asia/Shanghai'); use plugin\saiadmin\app\model\water\{WaterModel,WaterWithdrawalDemo,WaterOperator,WaterOrder,WaterAccount,WaterSku,WaterDelivery,WaterAgent,WaterLedger}; use plugin\saiadmin\app\service\WaterFinanceDashboard; const WITHDRAWAL_DEMO_BATCH = 'TEST-WATER-WITHDRAWAL-20260917'; function ensure(bool $ok, string $message): void { if (!$ok) throw new RuntimeException($message); } function protectedData(): array { $hashes = []; foreach ([WaterOrder::class, WaterAccount::class, WaterSku::class, WaterDelivery::class, WaterAgent::class] as $model) { $hashes[$model] = hash('sha256', serialize($model::query()->orderBy('id')->get()->toArray())); } return $hashes; } $samples = [ ['【提现测试】星澜商务', '代理佣金', '模拟微信账户', 576000], ['【提现测试】青禾推广用户', '推广奖励', '模拟微信账户', 86000], ['【提现测试】云帆企业服务', '代理佣金', '模拟银行账户', 328000], ['【提现测试】森屿商贸', '代理佣金', '模拟银行账户', 246000], ]; $result = ['applied' => false, 'planned' => 4, 'amount_cents' => 1236000, 'batch' => WITHDRAWAL_DEMO_BATCH]; if (in_array('--apply', $argv, true)) { $result = WaterModel::transaction(function () use ($samples): array { ensure((bool)WaterOperator::query()->where('id', 1)->lockForUpdate()->first(), '缺少操作账号'); $existing = WaterWithdrawalDemo::query()->where('batch', WITHDRAWAL_DEMO_BATCH)->orderBy('id')->get()->toArray(); if ($existing) { ensure(count($existing) === 4, '测试批次不完整,请核对,禁止自动覆盖'); return ['applied' => true, 'replayed' => true, 'created_ids' => array_column($existing, 'id')]; } $before = protectedData(); $service = new WaterFinanceDashboard(); $beforeDashboard = $service->dashboard(['month' => '2026-09']); $ids = []; foreach ($samples as $index => [$name, $kind, $channel, $cents]) { $ids[] = WaterWithdrawalDemo::query()->create([ 'batch' => WITHDRAWAL_DEMO_BATCH, 'reference' => WITHDRAWAL_DEMO_BATCH.'-'.($index + 1), 'applicant_name' => $name, 'kind_label' => $kind, 'channel_label' => $channel, 'amount_cents' => $cents, 'status' => 'pending', 'created_at' => '2026-09-17 00:00:00', ])->id; } $afterDashboard = $service->dashboard(['month' => '2026-09']); ensure($before === protectedData(), '现有业务数据发生变化'); foreach (['summary', 'trend', 'list', 'withdrawals', 'capabilities'] as $key) { ensure($beforeDashboard[$key] === $afterDashboard[$key], '真实财务口径发生变化:'.$key); } ensure($afterDashboard['withdrawal_demo']['total'] - $beforeDashboard['withdrawal_demo']['total'] === 4, '测试申请数量不正确'); ensure($afterDashboard['withdrawal_demo']['amount_cents'] - $beforeDashboard['withdrawal_demo']['amount_cents'] === 1236000, '测试申请金额不正确'); WaterLedger::query()->create(['kind' => 'demo_seed', 'reference' => WITHDRAWAL_DEMO_BATCH, 'created_by' => 1, 'enterprise_id' => 0, 'sku_id' => 0, 'quantity' => 0, 'note' => '提现展示测试4笔,合计12360元;无真实余额、无实际打款;IDs='.implode(',', $ids), 'created_at' => date('Y-m-d H:i:s')]); return ['applied' => true, 'replayed' => false, 'created_ids' => $ids]; }); $service = new WaterFinanceDashboard(); $all = $service->dashboard(['month' => '2026-09']); $demo = $service->dashboard(['month' => '2026-09', 'source' => 'demo']); ensure($all['withdrawal_demo'] === $demo['withdrawal_demo'], '测试范围不一致'); ensure($service->dashboard(['month' => '2026-09', 'source' => 'regular'])['withdrawal_demo']['total'] === 0, '排除测试时不应显示申请'); ensure($service->dashboard(['month' => '2026-08'])['withdrawal_demo']['total'] === 0, '测试申请不应跨月'); ensure($service->dashboard(['month' => '2026-09', 'keyword' => 'no-match', 'page' => 99])['withdrawal_demo'] === $all['withdrawal_demo'], '流水搜索和分页不应改变申请'); $before = new WaterFinanceDashboard(new DateTimeImmutable('2026-09-16 23:59:59')); ensure($before->dashboard(['month' => '2026-09'])['withdrawal_demo']['total'] === 0, '不应显示未来申请'); ensure($all['summary']['pending_withdrawal_cents'] === null && !$all['capabilities']['withdrawals'], '不可启用真实提现'); foreach ($all['withdrawal_demo']['list'] as $row) ensure($row['is_demo'] === true && $row['status_label'] === '待审核(测试)', '测试标记缺失'); $result['withdrawal_demo'] = $all['withdrawal_demo']; $result['checks'] = 'source/month/time filters, receipt independence, fixture labels and disabled payout capability passed'; } if (in_array('--http', $argv, true)) { $token = \Tinywan\Jwt\JwtToken::generateToken(['id' => 1, 'username' => 'admin', 'plat' => 'saiadmin', 'type' => 'web', 'client' => WITHDRAWAL_DEMO_BATCH, 'access_exp' => 120, 'refresh_exp' => 120])['access_token']; $cases = [['month' => '2026-09'], ['month' => '2026-09', 'source' => 'demo'], ['month' => '2026-09', 'source' => 'regular'], ['month' => '2026-08']]; foreach ($cases as $filters) { $ch = curl_init('http://127.0.0.1:8787/core/water/finance/dashboard?'.http_build_query($filters)); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_HTTPHEADER => ['Authorization: Bearer '.$token]]); $raw = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $body = json_decode((string)$raw, true); ensure($status === 200 && ($body['code'] ?? 0) === 200, 'HTTP看板失败'); ensure(($body['data']['withdrawal_demo'] ?? null) === (new WaterFinanceDashboard())->dashboard($filters)['withdrawal_demo'], 'HTTP测试申请与服务结果不一致'); } $result['http_read_checks'] = count($cases); } echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT).PHP_EOL;