user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $payload = [ 'month' => '2026-10', 'period' => ['start' => '2026-10-01', 'end' => '2026-10-31'], 'summary' => [ 'opening_balance' => 100.0, 'income' => 30.0, 'expense' => 10.0, 'balance' => 120.0, 'platform_count' => 8, 'available_platform_count' => 8, 'complete' => true, ], 'platforms' => [], ]; $service = Mockery::mock(StoreReceivingBalanceOverviewService::class); $service->shouldReceive('overview')->once()->with('2026-10')->andReturn($payload); $this->app->instance(StoreReceivingBalanceOverviewService::class, $service); $this->loginAs() ->getJson(route('ecommerce-finance.account-bills.overview', ['month' => '2026-10'])) ->assertOk() ->assertJsonPath('month', '2026-10') ->assertJsonPath('summary.balance', 120) ->assertJsonPath('summary.complete', true); } public function test_overview_endpoint_accepts_an_explicit_date_range(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $payload = [ 'month' => '2026-10', 'period' => ['start' => '2026-10-05', 'end' => '2026-10-20'], 'summary' => [ 'opening_balance' => 100.0, 'income' => 30.0, 'expense' => 10.0, 'balance' => 120.0, 'platform_count' => 8, 'available_platform_count' => 8, 'complete' => true, ], 'platforms' => [], ]; $service = Mockery::mock(StoreReceivingBalanceOverviewService::class); $service->shouldReceive('overview') ->once() ->with('2026-10-05', '2026-10-20') ->andReturn($payload); $this->app->instance(StoreReceivingBalanceOverviewService::class, $service); $this->loginAs() ->getJson(route('ecommerce-finance.account-bills.overview', [ 'start' => '2026-10-05', 'end' => '2026-10-20', ])) ->assertOk() ->assertJsonPath('period.start', '2026-10-05') ->assertJsonPath('period.end', '2026-10-20') ->assertJsonPath('summary.complete', true); } public function test_overview_page_uses_the_vue_mount_point(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $this->loginAs() ->get(route('business.pages.show', [ 'section' => 'billing-transactions', 'page' => 'store-receiving-bills', 'tab' => 'balance-overview', ])) ->assertOk() ->assertSee('class="biz-card mt-6 business-blank-page"', false) ->assertSee('public/js/business/pages.min.js', false); } public function test_reconciliation_page_uses_the_same_vue_entry_point(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $this->loginAs() ->get(route('business.pages.show', [ 'section' => 'billing-transactions', 'page' => 'period-balance-reconciliation', ])) ->assertOk() ->assertSee('class="biz-card mt-6 business-blank-page"', false) ->assertSee('public/js/business/pages.min.js', false); } public function test_reconciliation_endpoint_accepts_an_explicit_date_range(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $payload = [ 'month' => '2026-10', 'period' => ['start' => '2026-10-05', 'end' => '2026-10-20'], 'tolerance_yuan' => 200, 'financial' => ['summary' => ['balance' => 120.0], 'platforms' => []], 'dual_source' => ['status' => 'incomplete'], 'sources' => [], 'platforms' => [], 'stores' => [], 'summary' => ['connected_store_count' => 0], 'trend' => ['points' => [], 'by_platform' => [], 'by_store' => []], ]; $service = Mockery::mock(PeriodBalanceReconciliationService::class); $service->shouldReceive('build') ->once() ->with('2026-10-05', '2026-10-20') ->andReturn($payload); $this->app->instance(PeriodBalanceReconciliationService::class, $service); $this->loginAs() ->getJson(route('ecommerce-finance.account-bills.overview.reconciliation', [ 'start' => '2026-10-05', 'end' => '2026-10-20', ])) ->assertOk() ->assertJsonPath('period.start', '2026-10-05') ->assertJsonPath('period.end', '2026-10-20') ->assertJsonPath('tolerance_yuan', 200); } public function test_reconciliation_trend_endpoint_loads_separately(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $payload = [ 'points' => [[ 'date' => '2026-10-31', 'financial_balance' => 120.0, 'jushuitan_balance' => 110.0, 'difference' => 10.0, 'status' => 'normal', ]], 'by_platform' => [], 'by_store' => [], 'dates_note' => '趋势仅按现有聚水潭快照日期绘制,不推算缺失日期。', ]; $service = Mockery::mock(PeriodBalanceReconciliationService::class); $service->shouldReceive('buildTrend') ->once() ->with('2026-10-01', '2026-10-31') ->andReturn($payload); $this->app->instance(PeriodBalanceReconciliationService::class, $service); $this->loginAs() ->getJson(route('ecommerce-finance.account-bills.overview.reconciliation.trend', [ 'start' => '2026-10-01', 'end' => '2026-10-31', ])) ->assertOk() ->assertJsonPath('points.0.financial_balance', 120) ->assertJsonPath('points.0.status', 'normal'); } public function test_income_detail_endpoint_returns_platform_store_and_bill_item_rows(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $payload = [ 'month' => '2026-10', 'period' => ['start' => '2026-10-01', 'end' => '2026-10-31'], 'summary' => [ 'income' => 120.0, 'platform_count' => 8, 'available_platform_count' => 8, 'complete' => true, ], 'details' => [[ 'platform_slug' => 'taobao-alipay', 'platform' => '淘系支付宝账户', 'store_name' => '测试店铺', 'bill_item' => '货款收入', 'amount' => 120.0, ]], ]; $service = Mockery::mock(StoreReceivingBalanceOverviewService::class); $service->shouldReceive('incomeDetail')->once()->with('2026-10')->andReturn($payload); $this->app->instance(StoreReceivingBalanceOverviewService::class, $service); $this->loginAs() ->getJson(route('ecommerce-finance.account-bills.overview.income-detail', [ 'month' => '2026-10', ])) ->assertOk() ->assertJsonPath('summary.income', 120) ->assertJsonPath('details.0.platform_slug', 'taobao-alipay') ->assertJsonPath('details.0.bill_item', '货款收入'); } public function test_expense_detail_endpoint_returns_platform_store_and_bill_item_rows(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $payload = [ 'month' => '2026-10', 'period' => ['start' => '2026-10-01', 'end' => '2026-10-31'], 'summary' => [ 'expense' => 88.0, 'platform_count' => 8, 'available_platform_count' => 8, 'complete' => true, ], 'details' => [[ 'platform_slug' => 'jd-wallet', 'platform' => '京东网银钱包账户', 'store_name' => '测试店铺', 'bill_item' => '平台扣费', 'amount' => 88.0, ]], ]; $service = Mockery::mock(StoreReceivingBalanceOverviewService::class); $service->shouldReceive('expenseDetail')->once()->with('2026-10')->andReturn($payload); $this->app->instance(StoreReceivingBalanceOverviewService::class, $service); $this->loginAs() ->getJson(route('ecommerce-finance.account-bills.overview.expense-detail', [ 'month' => '2026-10', ])) ->assertOk() ->assertJsonPath('summary.expense', 88) ->assertJsonPath('details.0.platform_slug', 'jd-wallet') ->assertJsonPath('details.0.bill_item', '平台扣费'); } public function test_balance_detail_endpoint_returns_platform_closing_balances(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $payload = [ 'month' => '2026-10', 'period' => ['start' => '2026-10-01', 'end' => '2026-10-31'], 'summary' => [ 'balance' => 1049.07, 'platform_count' => 8, 'available_platform_count' => 8, 'complete' => true, ], 'details' => [[ 'platform_slug' => 'taobao-alipay', 'platform' => '淘系支付宝账户', 'store_name' => '', 'bill_item' => '', 'amount' => 218.88, ]], ]; $service = Mockery::mock(StoreReceivingBalanceOverviewService::class); $service->shouldReceive('balanceDetail')->once()->with('2026-10')->andReturn($payload); $this->app->instance(StoreReceivingBalanceOverviewService::class, $service); $this->loginAs() ->getJson(route('ecommerce-finance.account-bills.overview.balance-detail', [ 'month' => '2026-10', ])) ->assertOk() ->assertJsonPath('summary.balance', 1049.07) ->assertJsonPath('details.0.platform_slug', 'taobao-alipay') ->assertJsonPath('details.0.amount', 218.88); } public function test_balance_store_detail_endpoint_returns_store_closing_balances(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $payload = [ 'month' => '2026-10', 'period' => ['start' => '2026-10-01', 'end' => '2026-10-31'], 'summary' => [ 'balance' => 1049.07, 'platform_count' => 8, 'available_platform_count' => 8, 'store_count' => 1, 'ready_store_count' => 1, 'complete' => true, ], 'details' => [[ 'platform_slug' => 'taobao-alipay', 'platform' => '淘系支付宝账户', 'store_name' => '测试店铺', 'bill_item' => '', 'opening_balance' => 100.0, 'income' => 130.0, 'expense' => 10.0, 'amount' => 220.0, 'available' => true, ]], ]; $service = Mockery::mock(StoreReceivingBalanceOverviewService::class); $service->shouldReceive('balanceStoreDetail')->once()->with('2026-10')->andReturn($payload); $this->app->instance(StoreReceivingBalanceOverviewService::class, $service); $this->loginAs() ->getJson(route('ecommerce-finance.account-bills.overview.balance-store-detail', [ 'month' => '2026-10', ])) ->assertOk() ->assertJsonPath('summary.complete', true) ->assertJsonPath('details.0.store_name', '测试店铺') ->assertJsonPath('details.0.amount', 220); } }