Files
saasshop/tests/Feature/AdminPlatformOrderExportLedgerTest.php
2026-03-13 21:42:40 +00:00

196 lines
7.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Tests\Feature;
use App\Models\Merchant;
use App\Models\Plan;
use App\Models\PlatformOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AdminPlatformOrderExportLedgerTest extends TestCase
{
use RefreshDatabase;
protected function loginAsPlatformAdmin(): void
{
$this->seed();
$this->post('/admin/login', [
'email' => 'platform.admin@demo.local',
'password' => 'Platform@123456',
])->assertRedirect('/admin');
}
public function test_export_ledger_should_download_csv_with_bom_and_headers(): void
{
$this->loginAsPlatformAdmin();
$merchant = Merchant::query()->firstOrFail();
$plan = Plan::query()->create([
'code' => 'po_export_ledger_plan',
'name' => '平台订单导出对账明细测试套餐',
'billing_cycle' => 'monthly',
'price' => 10,
'list_price' => 10,
'status' => 'active',
'sort' => 10,
'published_at' => now(),
]);
$order = PlatformOrder::query()->create([
'merchant_id' => $merchant->id,
'plan_id' => $plan->id,
'order_no' => 'PO_EXPORT_LEDGER_0001',
'order_type' => 'new_purchase',
'status' => 'pending',
'payment_status' => 'unpaid',
'plan_name' => $plan->name,
'billing_cycle' => $plan->billing_cycle,
'period_months' => 1,
'quantity' => 1,
'payable_amount' => 10,
'paid_amount' => 0,
'placed_at' => now(),
'meta' => [
'payment_receipts' => [
[
'type' => 'bank_transfer',
'channel' => 'offline',
'amount' => 10,
'paid_at' => now()->toDateTimeString(),
'note' => 'test-pay',
'created_at' => now()->toDateTimeString(),
'admin_id' => 1,
],
],
'refund_receipts' => [
[
'type' => 'refund',
'channel' => 'offline',
'amount' => 1,
'refunded_at' => now()->toDateTimeString(),
'note' => 'test-refund',
'created_at' => now()->toDateTimeString(),
'admin_id' => 1,
],
],
],
]);
$res = $this->get('/admin/platform-orders/' . $order->id . '/export-ledger?download=1');
$res->assertOk();
$content = $res->streamedContent();
$res2 = $this->get('/admin/platform-orders/' . $order->id . '/export-ledger?download=1&include_order_snapshot=1');
$res2->assertOk();
$content2 = $res2->streamedContent();
// UTF-8 BOM
$this->assertStringStartsWith("\xEF\xBB\xBF", $content);
$this->assertStringStartsWith("\xEF\xBB\xBF", $content2);
// exported_at 摘要行
$this->assertStringContainsString('exported_at,', $content);
$this->assertStringContainsString('exported_at,', $content2);
// 核心表头
$this->assertStringContainsString('record_type,receipt_type,channel,amount,biz_time,created_at,admin_id,note', $content);
$this->assertStringContainsString('record_type,receipt_type,channel,amount,biz_time,created_at,admin_id,note', $content2);
// 至少包含一条 payment 与一条 refund 行(包含 type 字段)
$this->assertStringContainsString('payment,bank_transfer,offline,10', $content);
$this->assertStringContainsString('refund,refund,offline,1', $content);
// include_order_snapshot=1 时应包含更多摘要字段
$this->assertStringContainsString("merchant_id,{$merchant->id}", $content2);
$this->assertStringContainsString('payment_status,unpaid', $content2);
$this->assertStringContainsString('site_subscription_id,', $content2);
// 对账/退款汇总字段
$this->assertStringContainsString('receipt_total,10.00', $content2);
$this->assertStringContainsString('refund_total,1.00', $content2);
$this->assertStringContainsString('reconcile_delta,10.00', $content2);
$this->assertStringContainsString('amount_tolerance,0.01', $content2);
$this->assertStringContainsString('reconcile_mismatch,1', $content2);
$this->assertStringContainsString('refund_inconsistent,0', $content2);
// 口径说明行(便于离线流转自解释)
$this->assertStringContainsString('reconcile_mismatch_rule,', $content2);
$this->assertStringContainsString('refund_inconsistent_rule,', $content2);
}
public function test_show_page_should_render_export_ledger_link(): void
{
$this->loginAsPlatformAdmin();
$merchant = Merchant::query()->firstOrFail();
$plan = Plan::query()->create([
'code' => 'po_export_ledger_link_plan',
'name' => '平台订单详情导出对账明细链接测试套餐',
'billing_cycle' => 'monthly',
'price' => 10,
'list_price' => 10,
'status' => 'active',
'sort' => 10,
'published_at' => now(),
]);
$order = PlatformOrder::query()->create([
'merchant_id' => $merchant->id,
'plan_id' => $plan->id,
'order_no' => 'PO_EXPORT_LEDGER_LINK_0001',
'order_type' => 'new_purchase',
'status' => 'pending',
'payment_status' => 'unpaid',
'plan_name' => $plan->name,
'billing_cycle' => $plan->billing_cycle,
'period_months' => 1,
'quantity' => 1,
'payable_amount' => 10,
'paid_amount' => 0,
'placed_at' => now(),
'meta' => [],
]);
$res = $this->get('/admin/platform-orders/' . $order->id);
$res->assertOk();
$html = $res->getContent();
// 页面应包含两条导出链接(解析 HTML按 query 键值断言,避免参数顺序导致脆弱)
preg_match_all('/href="([^"]+)"/', $html, $matches);
$hrefs = $matches[1] ?? [];
$exportUrls = array_values(array_filter($hrefs, fn ($u) => str_contains($u, '/admin/platform-orders/' . $order->id . '/export-ledger')));
$this->assertGreaterThanOrEqual(2, count($exportUrls));
$foundBase = false;
$foundWithSnapshot = false;
foreach ($exportUrls as $u) {
$parts = parse_url($u);
parse_str($parts['query'] ?? '', $q);
if (($q['download'] ?? null) === '1' && !isset($q['include_order_snapshot'])) {
$foundBase = true;
}
if (($q['download'] ?? null) === '1' && (string)($q['include_order_snapshot'] ?? '') === '1') {
$foundWithSnapshot = true;
}
}
$this->assertTrue($foundBase, '未找到 download=1 的基础导出链接');
$this->assertTrue($foundWithSnapshot, '未找到 download=1 且 include_order_snapshot=1 的导出链接');
$res->assertSee('导出对账明细CSV', false);
$res->assertSee('导出含订单摘要CSV', false);
// 导出链接应新开窗口,避免离开当前详情页
$res->assertSee('target="_blank"', false);
}
}