'sqlite']); app('url')->defaults(['company_id' => $this->company->id]); $this->createFinanceTables(); $this->seedFinanceData(); } public function test_selected_shop_locks_account_and_builds_balanced_flow_summary(): void { $data = app(ShopFlowQuery::class)->pageData(new Request([ 'company' => '测试主体', 'shop' => '测试店铺A', 'account' => '其他账户', 'start' => '2026-11-01', 'end' => '2026-11-02', 'direction' => 'all', 'source_type' => 'all', ])); $this->assertSame('测试账户', $data['shopFlowSelectedAccountShort']); $this->assertSame('测试店铺A', $data['shopFlowSelectedShop']['shop_name']); $this->assertCount(2, $data['shopFlowRelatedShops']); $this->assertSame(4, $data['shopFlowSummary']['income_count']); $this->assertSame(60.0, $data['shopFlowSummary']['income']); $this->assertSame(3, $data['shopFlowSummary']['withdraw_count']); $this->assertSame(1, $data['shopFlowSummary']['pending_count']); $this->assertSame(1, $data['shopFlowSummary']['expense_count']); $this->assertSame(25.0, $data['shopFlowSummary']['expense']); $this->assertSame('matched', $data['shopFlowBalance']['status']); $this->assertSame(135.0, $data['shopFlowBalance']['closing']); $this->assertCount(5, $data['shopFlowRows']->items()); $this->assertSame(135.0, $data['shopFlowRows']->items()[0]['balance']); $this->assertStringContainsString( 'account=' . rawurlencode('测试账户'), $data['shopFlowUnlockUrl'], ); $shopRows = collect($data['shopFlowRows']->items()) ->where('source', 'shop') ->values(); $matched = $shopRows->firstWhere('match_status', '已匹配提现单'); $pending = $shopRows->firstWhere('match_status', '待匹配提现单'); $this->assertSame('测试店铺A', $matched['shop_name']); $this->assertSame('', $pending['shop_name']); } public function test_detail_filters_do_not_change_period_summary_or_balance_check(): void { $data = app(ShopFlowQuery::class)->pageData(new Request([ 'account' => '测试账户', 'start' => '2026-11-01', 'end' => '2026-11-02', 'direction' => 'expense', 'source_type' => 'dingtalk', ])); $this->assertNull($data['shopFlowSelectedShop']); $this->assertCount(2, $data['shopFlowRelatedShops']); $this->assertSame(60.0, $data['shopFlowSummary']['income']); $this->assertSame(25.0, $data['shopFlowSummary']['expense']); $this->assertCount(1, $data['shopFlowRows']->items()); $this->assertSame('expense_dingtalk', $data['shopFlowRows']->items()[0]['type_key']); $this->assertSame('matched', $data['shopFlowBalance']['status']); } public function test_page_renders_with_export_and_ledger_table_classes(): void { $this->loginAs() ->get(route('business.pages.show', [ 'section' => 'financial-operations', 'page' => 'shop-flows', ]) . '?shop=' . rawurlencode('测试店铺A') . '&start=2026-11-01&end=2026-11-02') ->assertOk() ->assertSeeText('店铺收支明细') ->assertSeeText('测试店铺A') ->assertSeeText('¥60.00') ->assertSeeText('查看整张卡') ->assertSee('data-shop-flow-page', false) ->assertSee('data-shop-flow-export', false) ->assertSee('ledger-table', false) ->assertSee('/workspace/financial-operations/shop-flows/shop-flows/export', false); } public function test_csv_export_streams_filtered_rows_with_utf8_bom(): void { $url = route('business.pages.export', [ 'section' => 'financial-operations', 'page' => 'shop-flows', 'tab' => 'shop-flows', ]) . '?' . http_build_query([ 'shop' => '测试店铺A', 'start' => '2026-11-01', 'end' => '2026-11-02', 'direction' => 'income', 'source_type' => 'shop', ]); $request = Request::create($url); $this->assertSame('测试店铺A', $request->query('shop')); $this->assertSame( '测试账户', app(ShopFlowQuery::class)->pageData($request)['shopFlowSelectedAccountShort'], ); $directResponse = app(ShopFlowQuery::class)->export($request); ob_start(); $directResponse->sendContent(); $directContent = ob_get_clean(); $this->assertStringContainsString('测试店铺A', $directContent); $response = $this->loginAs()->get($url)->assertOk(); $response->assertHeader('content-type', 'text/csv; charset=UTF-8'); $content = $response->streamedContent(); $this->assertStringStartsWith("\xEF\xBB\xBF", $content); $this->assertStringContainsString('测试店铺A', $content); $this->assertStringContainsString('已匹配提现单', $content); $this->assertStringContainsString('待匹配提现单', $content); $this->assertStringNotContainsString('供应商退款', $content); $this->assertStringNotContainsString('付款审批', $content); } private function createFinanceTables(): void { Schema::connection('sqlite')->create('fin_companies', function ($table): void { $table->increments('id'); $table->string('name'); $table->string('tax_type')->nullable(); $table->string('basic_bank')->nullable(); }); Schema::connection('sqlite')->create('fin_accounts', function ($table): void { $table->increments('id'); $table->string('company_name'); $table->string('account_short'); $table->string('account_no')->nullable(); $table->string('bank_name')->nullable(); $table->string('account_last4')->nullable(); $table->decimal('current_balance', 18, 2)->default(0); $table->string('owner')->nullable(); }); Schema::connection('sqlite')->create('fin_shops', function ($table): void { $table->increments('id'); $table->string('shop_name'); $table->string('platform')->nullable(); $table->string('company_name')->nullable(); $table->string('account_short')->nullable(); $table->string('status')->nullable(); $table->string('payout_account')->nullable(); }); Schema::connection('sqlite')->create('fin_transactions', function ($table): void { $table->increments('id'); $table->date('txn_date'); $table->string('account_short')->nullable(); $table->text('detail')->nullable(); $table->decimal('income', 18, 2)->default(0); $table->decimal('expense', 18, 2)->default(0); $table->string('approval_no')->nullable(); $table->string('source'); $table->string('source_value')->nullable(); $table->string('process_type')->nullable(); $table->string('row_key'); }); Schema::connection('sqlite')->create('fin_daily_flows', function ($table): void { $table->increments('id'); $table->date('flow_date'); $table->string('account_short'); $table->decimal('closing_balance', 18, 2)->default(0); }); Schema::connection('sqlite')->create('fin_account_opening', function ($table): void { $table->increments('id'); $table->string('account_short'); $table->date('opening_date'); $table->decimal('opening_balance', 18, 2)->default(0); }); Schema::connection('sqlite')->create('fin_account_map', function ($table): void { $table->increments('id'); $table->string('source'); $table->string('source_value'); $table->string('account_short')->nullable(); }); } private function seedFinanceData(): void { DB::table('fin_companies')->insert([ 'name' => '测试主体', 'tax_type' => '一般纳税人', 'basic_bank' => '测试银行', ]); DB::table('fin_accounts')->insert([ 'company_name' => '测试主体', 'account_short' => '测试账户', 'account_no' => '6222000012345678', 'bank_name' => '测试银行', 'account_last4' => '5678', 'current_balance' => 120, 'owner' => '测试户名', ]); DB::table('fin_shops')->insert([ [ 'shop_name' => '测试店铺A', 'platform' => '京东', 'company_name' => '测试主体', 'account_short' => '测试账户', 'status' => '在营', 'payout_account' => '测试收款账号', ], [ 'shop_name' => '测试店铺B', 'platform' => '京东', 'company_name' => '测试主体', 'account_short' => '测试账户', 'status' => '在营', 'payout_account' => '测试收款账号', ], ]); DB::table('fin_daily_flows')->insert([ [ 'flow_date' => '2026-10-31', 'account_short' => '测试账户', 'closing_balance' => 100, ], [ 'flow_date' => '2026-11-02', 'account_short' => '测试账户', 'closing_balance' => 135, ], ]); DB::table('fin_account_opening')->insert([ 'account_short' => '测试账户', 'opening_date' => '2026-10-01', 'opening_balance' => 80, ]); DB::table('fin_transactions')->insert([ [ 'txn_date' => '2026-11-01', 'account_short' => '测试账户', 'detail' => '京东账单提现 · 测试店铺A', 'income' => 30, 'expense' => 0, 'approval_no' => 'JD:W001', 'source' => 'shop', 'source_value' => '测试店铺A', 'process_type' => '店铺提现', 'row_key' => 'shop-match', ], [ 'txn_date' => '2026-11-01', 'account_short' => '测试账户', 'detail' => '供应商退款', 'income' => 10, 'expense' => 0, 'approval_no' => 'PAY-001', 'source' => 'payable', 'source_value' => '供应商甲', 'process_type' => '供应商退款', 'row_key' => 'other-income', ], [ 'txn_date' => '2026-11-01', 'account_short' => '测试账户', 'detail' => '店铺提现待匹配', 'income' => 5, 'expense' => 0, 'approval_no' => 'SHOP-UNMATCHED', 'source' => 'shop', 'source_value' => null, 'process_type' => '店铺提现', 'row_key' => 'shop-pending', ], [ 'txn_date' => '2026-11-02', 'account_short' => '', 'detail' => '付款审批', 'income' => 0, 'expense' => 25, 'approval_no' => 'DING-001', 'source' => 'dingtalk', 'source_value' => '测试账户', 'process_type' => '付款审批', 'row_key' => 'ding-expense', ], [ 'txn_date' => '2026-11-01', 'account_short' => '测试账户', 'detail' => '店铺账单提现 · 测试店铺B', 'income' => 15, 'expense' => 0, 'approval_no' => 'BILL-001', 'source' => 'shop_bill', 'source_value' => '测试店铺B', 'process_type' => '店铺提现', 'row_key' => 'shop-bill-match', ], ]); DB::table('fin_account_map')->insert([ [ 'source' => 'payable', 'source_value' => '供应商甲', 'account_short' => '测试账户', ], [ 'source' => 'dingtalk', 'source_value' => '测试账户', 'account_short' => '测试账户', ], ]); } }