user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); DB::connection('business_mirror')->table('fin_accounts')->insert([ 'company_name' => '测试财务公司', 'account_short' => '测试账户', 'account_no' => '6222000000000001', 'bank_name' => '测试银行', 'company_plan' => '保留', 'current_balance' => 321.45, 'blocked_amount' => 0, 'daily_available' => 321.45, ]); } private function pageUrl(): string { return route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', ]); } private function unavailableBalancesUrl(): string { return route('business.financial-ledger.unavailable-balances', [ 'company_id' => $this->company->id, ]); } private function accountTodoUrl(): string { return route('business.financial-ledger.accounts.todo', [ 'company_id' => $this->company->id, ]); } private function statusUrl(): string { return route('business.financial-ledger.accounts.status', [ 'company_id' => $this->company->id, ]); } private function typeUrl(): string { return route('business.financial-ledger.accounts.type', [ 'company_id' => $this->company->id, ]); } private function planUrl(): string { return route('business.financial-ledger.accounts.plan', [ 'company_id' => $this->company->id, ]); } private function keepUrl(): string { return route('business.financial-ledger.accounts.keep', [ 'company_id' => $this->company->id, ]); } private function createAccountUrl(): string { return route('business.financial-ledger.accounts.store', [ 'company_id' => $this->company->id, ]); } public function test_account_table_renders_status_selector_with_four_options(): void { $this->loginAs()->get($this->pageUrl()) ->assertOk() ->assertSee('账户状态') ->assertSee('data-account-status', false) ->assertSee('正常') ->assertSee('网银异常') ->assertSee('限额') ->assertSee('工商注销') ->assertSee('name="accounts_short"', false) ->assertSee('name="accounts_status"', false) ->assertSee('name="accounts_region"', false) ->assertSee('name="accounts_type"', false) ->assertSee('name="accounts_balance"', false) ->assertSee('0-5万') ->assertSee('5-20万') ->assertSee('20万以上') ->assertSee('data-account-type', false) ->assertSee('data-account-plan', false) ->assertSee('data-account-keep', false) ->assertSee('data-account-plan-display', false) ->assertSee('data-account-keep-display', false) ->assertSee('ledger-type-select', false) ->assertSee('data-mapping-bulk', false) ->assertSee('导出录入模板') ->assertSee('data-account-template-download', false) ->assertSee('data-account-template-download-status', false) ->assertSee('data-ledger-header-field="account_short"', false) ->assertSee('data-ledger-header-field="account_type"', false) ->assertSee('data-ledger-header-field="account_status"', false) ->assertSee('data-ledger-header-field="company_plan"', false) ->assertSee('data-ledger-header-field="account_should_keep"', false) ->assertSee('data-ledger-header-details', false) ->assertSee('aria-label="账户状态排序和筛选"', false) ->assertSee('data-account-short="测试账户" data-account-should-keep="是"', false) ->assertSee('网商银行') ->assertSee('ledger-accounts-table-wrap', false) ->assertSee('ledger-pagination__controls', false) ->assertSee('ledger-page-box', false) ->assertSee('data-ledger-page-select', false) ->assertSee('data-ledger-pagination="accounts"', false) ->assertSee('data-ledger-pagination="shops"', false) ->assertSee('data-ledger-pagination="flows"', false) ->assertSee('data-ledger-pagination="transactions"', false) ->assertSee('data-ledger-pagination="mapping"', false) ->assertSee('1 / 1', false) ->assertSee('data-ledger-column-picker', false) ->assertSee('data-ledger-column-panel', false) ->assertSee('data-ledger-edit-mode-toggle', false) ->assertSee('data-ledger-add-company', false) ->assertSee('data-ledger-company-dialog', false) ->assertSee('新增公司/账户') ->assertSee('name="company_name"', false) ->assertSee('name="company_register_place"', false) ->assertSee('name="company_business_type"', false) ->assertSee('name="company_tax_type"', false) ->assertSee('name="company_status"', false) ->assertSee('name="company_basic_bank"', false) ->assertSee('name="company_basic_account_last4"', false) ->assertSee('name="company_remark"', false) ->assertSee('name="opening_balance"', false) ->assertSee('id="ledger-company-options"', false) ->assertSee('aria-checked="false"', false) ->assertSee('ledger-account-editor', false) ->assertSee('data-account-edit-display', false) ->assertSee('列设置') ->assertSee('拖动字段可调整列顺序') ->assertSee('拖动表头右侧边界可调整列宽') ->assertSee('恢复默认') ->assertSee('可用金额合计') ->assertSee('不可用金额合计') ->assertSee('可用金额合计(万元)') ->assertSee('href="' . $this->unavailableBalancesUrl() . '"', false) ->assertSee('查看账户异常待办') ->assertSee('正常计可用,异常计不可用') ->assertSee('data-ledger-header-field="current_balance"', false) ->assertSee('data-ledger-header-field="daily_available"', false) ->assertSee('data-ledger-header-field="blocked_amount"', false) ->assertSee('data-ledger-summary-available', false) ->assertSee('data-account-current-balance="321.45"', false) ->assertSee('data-account-available-amount', false) ->assertSee('data-account-blocked-amount', false) ->assertSee('未填写') ->assertDontSee('data-account-filter-state', false) ->assertSee('value="normal" selected', false); } public function test_account_balance_filter_uses_non_overlapping_ranges(): void { DB::connection('business_mirror')->table('fin_accounts')->insert([ ['company_name' => '余额筛选公司', 'account_short' => '余额-负数', 'current_balance' => -1], ['company_name' => '余额筛选公司', 'account_short' => '余额-0', 'current_balance' => 0], ['company_name' => '余额筛选公司', 'account_short' => '余额-49999', 'current_balance' => 49999.99], ['company_name' => '余额筛选公司', 'account_short' => '余额-50000', 'current_balance' => 50000], ['company_name' => '余额筛选公司', 'account_short' => '余额-199999', 'current_balance' => 199999.99], ['company_name' => '余额筛选公司', 'account_short' => '余额-200000', 'current_balance' => 200000], ]); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_balance' => '0-5', ])) ->assertOk() ->assertSee('data-account-short="余额-0"', false) ->assertSee('data-account-short="余额-49999"', false) ->assertDontSee('data-account-short="余额-负数"', false) ->assertDontSee('data-account-short="余额-50000"', false) ->assertSee('value="0-5" selected', false); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_balance' => '5-20', ])) ->assertOk() ->assertSee('data-account-short="余额-50000"', false) ->assertSee('data-account-short="余额-199999"', false) ->assertDontSee('data-account-short="余额-49999"', false) ->assertDontSee('data-account-short="余额-200000"', false); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_balance' => '20-plus', ])) ->assertOk() ->assertSee('data-account-short="余额-200000"', false) ->assertDontSee('data-account-short="余额-199999"', false); } public function test_account_table_monetary_columns_are_displayed_in_ten_thousands(): void { DB::connection('business_mirror')->table('fin_accounts') ->where('account_short', '测试账户') ->update([ 'current_balance' => 123456.78, 'daily_available' => 120000, 'blocked_amount' => 3456.78, 'single_limit' => '200000/10000', 'daily_limit' => 300000, 'monthly_limit' => 400000, 'yearly_limit' => 500000, 'raise_target' => 50000, ]); $this->loginAs()->get($this->pageUrl()) ->assertOk() ->assertSee('当前余额(万元)') ->assertSee('单笔限额(万元)') ->assertSee('title="123,456.78 元"', false) ->assertSee('>12.35', false) ->assertSee('>12.00', false) ->assertSee('>0.35', false) ->assertSee('>20.00 / 1.00', false) ->assertSee('>5.00', false) ->assertSee('20.00 / 1.00', false) ->assertSee('30.00', false) ->assertSee('40.00', false) ->assertSee('50.00', false); } public function test_unavailable_balance_tasks_page_is_separate_and_only_lists_blocked_accounts(): void { $connection = DB::connection('business_mirror'); $accounts = [ [ 'company_name' => '待办测试公司', 'account_short' => '待办测试账户', 'account_no' => '6222000000000099', 'account_type' => '公户', 'bank_name' => '待办测试银行', 'bank_city_branch' => '上海 / 待办测试支行', 'current_balance' => 100, 'blocked_amount' => 100, 'daily_available' => 0, 'owner' => '待办负责人', 'next_action' => '核实冻结原因', 'remark' => '独立待办备注', ], [ 'company_name' => '零金额测试公司', 'account_short' => '零金额测试账户', 'current_balance' => 100, 'blocked_amount' => 0, 'daily_available' => 100, ], [ 'company_name' => '正常状态测试公司', 'account_short' => '正常状态测试账户', 'current_balance' => 100, 'blocked_amount' => 0, 'daily_available' => 100, ], [ 'company_name' => '待办测试公司二', 'account_short' => '待办测试账户二', 'account_no' => '6222000000000100', 'account_type' => '个人卡', 'bank_name' => '待办测试银行二', 'bank_city_branch' => '杭州 / 待办测试支行', 'current_balance' => 250, 'blocked_amount' => 250, 'daily_available' => 0, ], ]; foreach ($accounts as $account) { $connection->table('fin_accounts')->insert($account); } $connection->table('fin_account_statuses')->insert([ [ 'account_short' => '待办测试账户', 'status' => 'limited', 'updated_by' => 'Test User', ], [ 'account_short' => '正常状态测试账户', 'status' => 'normal', 'updated_by' => 'Test User', ], [ 'account_short' => '待办测试账户二', 'status' => 'online_banking_abnormal', 'updated_by' => 'Test User', ], ]); $connection->table('fin_account_type_overrides')->insert([ 'account_short' => '待办测试账户', 'account_type' => '网商银行', 'updated_by' => 'Test User', ]); $this->loginAs()->get($this->unavailableBalancesUrl()) ->assertOk() ->assertSee('账户异常待办') ->assertSee('不与其他页面的待办事项合并') ->assertSee('待办测试公司') ->assertSee('data-pending-account="待办测试账户"', false) ->assertSee('不可用金额合计') ->assertSee('350.00') ->assertSee('限额') ->assertSee('网商银行') ->assertSee('6222000000000099') ->assertSee('待办测试银行') ->assertSee('上海 / 待办测试支行') ->assertSee('异常原因') ->assertSee('办理方式') ->assertSee('当前进度') ->assertSee('是否完成') ->assertSee('当前余额', false) ->assertSee('data-pending-current-balance="100.00"', false) ->assertSee('data-pending-current-balance-total="350.00"', false) ->assertSee('当前筛选合计') ->assertSee('name="pending_short"', false) ->assertSee('name="pending_type"', false) ->assertSee('name="pending_status"', false) ->assertSee('name="pending_reason"', false) ->assertSee('name="pending_method"', false) ->assertSee('name="pending_owner"', false) ->assertSee('按开始时间排序') ->assertSee('按完成时间排序') ->assertDontSee('name="pending_q"', false) ->assertSee('data-pending-tbody', false) ->assertSee('data-pending-pagination', false) ->assertSee('data-pending-table', false) ->assertSee('拖动表头右侧边界可调整列宽') ->assertSee('const pendingColumnDefaultWidths = Object.freeze({', false) ->assertSee('company_name: 170', false) ->assertSee('completion_status: 105', false) ->assertSee('ledger-page-box', false) ->assertSee('1 / 1', false) ->assertSee('data-pending-account="待办测试账户二"', false) ->assertDontSee('data-pending-account="正常状态测试账户"', false) ->assertDontSee('data-pending-account="零金额测试账户"', false); $this->loginAs()->post($this->accountTodoUrl(), [ 'account_short' => '待办测试账户', 'abnormal_reason' => '账户冻结', 'handling_method' => 'online', 'owner_name' => '待办负责人', 'started_at' => '2026-10-21T09:00', 'completed_at' => '2026-10-21T11:30', 'current_progress' => '处理中', 'completion_status' => 'in_progress', ])->assertRedirect(); $this->assertDatabaseHas('fin_account_todos', [ 'account_short' => '待办测试账户', 'abnormal_reason' => '账户冻结', 'handling_method' => 'online', 'owner_name' => '待办负责人', 'current_progress' => '处理中', 'completion_status' => 'in_progress', 'updated_by' => 'Test User', ], 'business_mirror'); $connection->table('fin_account_todos')->insert([ 'account_short' => '待办测试账户二', 'abnormal_reason' => '网银证书过期', 'handling_method' => 'offline', 'owner_name' => '其他负责人', 'started_at' => '2026-10-21 10:00:00', 'current_progress' => '待联系银行', 'completion_status' => 'not_started', 'updated_by' => 'Test User', ]); $this->loginAs()->get($this->unavailableBalancesUrl()) ->assertOk() ->assertSee('账户冻结') ->assertSee('待办负责人') ->assertSee('处理中') ->assertSee('进行中'); $filteredUrl = $this->unavailableBalancesUrl() . '?' . http_build_query([ 'pending_short' => '待办测试账户', 'pending_type' => '网商银行', 'pending_status' => 'limited', 'pending_reason' => '账户冻结', 'pending_method' => 'online', 'pending_owner' => '待办负责人', ]); $this->loginAs()->get($filteredUrl) ->assertOk() ->assertSee('data-pending-account="待办测试账户"', false) ->assertSee('data-pending-current-balance-total="100.00"', false) ->assertDontSee('data-pending-account="待办测试账户二"', false); $connection->table('fin_accounts') ->where('account_short', '待办测试账户') ->update(['current_balance' => 180]); $this->loginAs()->get($filteredUrl) ->assertOk() ->assertSee('data-pending-current-balance="180.00"', false) ->assertSee('data-pending-current-balance-total="180.00"', false); $sortResponse = $this->loginAs()->get( $this->unavailableBalancesUrl() . '?pending_sort=started_at&pending_direction=desc' )->assertOk()->assertSee('aria-sort="descending"', false); preg_match_all('/data-pending-account="([^"]+)"/u', $sortResponse->getContent(), $sortedAccounts); $this->assertSame(['待办测试账户二', '待办测试账户'], array_values(array_intersect( $sortedAccounts[1], ['待办测试账户', '待办测试账户二'], ))); $this->loginAs()->post($this->accountTodoUrl(), [ 'account_short' => '待办测试账户', 'abnormal_reason' => '账户冻结', 'handling_method' => 'online', 'owner_name' => '待办负责人', 'started_at' => '2026-10-21T09:00', 'completed_at' => '2026-10-21T12:00', 'current_progress' => '已处理', 'completion_status' => 'completed', ])->assertRedirect(); $this->assertDatabaseHas('fin_account_statuses', [ 'account_short' => '待办测试账户', 'status' => 'normal', ], 'business_mirror'); $this->assertDatabaseHas('fin_accounts', [ 'account_short' => '待办测试账户', 'current_balance' => 180, 'daily_available' => 180, 'blocked_amount' => 0, ], 'business_mirror'); $this->loginAs()->get($this->unavailableBalancesUrl()) ->assertOk() ->assertDontSee('data-pending-account="待办测试账户"', false); } public function test_financial_ledger_tab_forms_submit_to_query_tab_routes(): void { $pageUrl = $this->pageUrl(); $response = $this->loginAs()->get($pageUrl)->assertOk(); foreach (['accounts', 'shops', 'flows', 'transactions', 'mapping'] as $tab) { $response->assertSee('action="' . $pageUrl . '?tab=' . $tab . '"', false); $response->assertDontSee('action="' . $pageUrl . '/' . $tab . '"', false); } } public function test_explicit_account_keep_value_takes_precedence_over_company_plan(): void { DB::connection('business_mirror')->table('fin_accounts')->insert([ 'company_name' => '保留判断测试公司', 'account_short' => '显式不保留账户', 'company_plan' => '保留', 'account_should_keep' => '否', ]); $this->loginAs()->get($this->pageUrl()) ->assertOk() ->assertSee('data-account-short="显式不保留账户" data-account-should-keep="否"', false); } public function test_account_pagination_lists_page_urls_with_current_filter(): void { $rows = []; for ($index = 1; $index <= 101; $index++) { $rows[] = [ 'company_name' => '分页测试公司', 'account_short' => 'page-test-' . $index, ]; } DB::connection('business_mirror')->table('fin_accounts')->insert($rows); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_short' => 'page-test', ])) ->assertOk() ->assertSee('ledger-page-box', false) ->assertSee('data-ledger-page-select', false) ->assertSee('aria-label="下一页"', false) ->assertSee('accounts_page=2', false) ->assertSee('accounts_short=page-test', false); } public function test_account_filters_narrow_by_short_status_region_and_type(): void { $connection = DB::connection('business_mirror'); $connection->table('fin_accounts')->insert([ [ 'company_name' => '筛选测试公司甲', 'account_short' => '筛选账户甲', 'region' => '上海', 'account_type' => '公户', ], [ 'company_name' => '筛选测试公司乙', 'account_short' => '筛选账户乙', 'region' => '杭州', 'account_type' => '个人卡', ], ]); $connection->table('fin_account_statuses')->insert([ 'account_short' => '筛选账户乙', 'status' => 'limited', 'updated_by' => 'Test User', ]); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_short' => '筛选账户', 'accounts_status' => 'limited', 'accounts_region' => '杭州', 'accounts_type' => '个人卡', ])) ->assertOk() ->assertSee('data-account-short="筛选账户乙"', false) ->assertDontSee('data-account-short="筛选账户甲"', false) ->assertSee('data-account-filter-state', false) ->assertSee('筛选中') ->assertSee('value="limited" selected', false); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_short' => '筛选账户', 'accounts_status' => 'normal', ])) ->assertOk() ->assertSee('data-account-short="筛选账户甲"', false) ->assertDontSee('data-account-short="筛选账户乙"', false); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_short' => '测试账户', 'accounts_status' => 'normal', 'accounts_region' => '__empty__', 'accounts_type' => '__empty__', ])) ->assertOk() ->assertSee('data-account-short="测试账户"', false); } public function test_account_type_can_be_saved_and_filtered_by_effective_value(): void { $connection = DB::connection('business_mirror'); $connection->table('fin_accounts')->insert([ [ 'company_name' => '类型测试公司甲', 'account_short' => '类型账户甲', 'account_type' => '公户', ], [ 'company_name' => '类型测试公司乙', 'account_short' => '类型账户乙', 'account_type' => '个人卡', ], [ 'company_name' => '类型测试公司丙', 'account_short' => '类型账户丙', 'account_type' => '支付宝', ], ]); $this->loginAs()->postJson($this->typeUrl(), [ 'account_short' => '类型账户甲', 'account_type' => '网商银行', ]) ->assertOk() ->assertJsonPath('account_type', '网商银行') ->assertJsonPath('label', '网商银行'); $this->loginAs()->postJson($this->typeUrl(), [ 'account_short' => '类型账户甲', 'account_type' => '个人卡', ])->assertOk(); $this->assertDatabaseCount('fin_account_type_overrides', 1, 'business_mirror'); $this->assertDatabaseHas('fin_account_type_overrides', [ 'account_short' => '类型账户甲', 'account_type' => '个人卡', 'updated_by' => 'Test User', ], 'business_mirror'); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_type' => '个人卡', ])) ->assertOk() ->assertSee('data-account-short="类型账户甲"', false) ->assertSee('data-account-short="类型账户乙"', false) ->assertDontSee('data-account-short="类型账户丙"', false) ->assertSee('value="个人卡" selected', false); $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_type' => '公户', ])) ->assertOk() ->assertDontSee('data-account-short="类型账户甲"', false); $this->withExceptionHandling()->loginAs()->postJson($this->typeUrl(), [ 'account_short' => '类型账户甲', 'account_type' => '不存在的类型', ])->assertUnprocessable()->assertJsonPath('message', '请选择现有账户类型。'); } public function test_account_type_change_derives_last4_and_alipay_uses_slash(): void { DB::connection('business_mirror')->table('fin_accounts') ->where('account_short', '测试账户') ->update([ 'account_no' => '6222-1234 5678', 'account_last4' => '0000', ]); $this->loginAs()->postJson($this->typeUrl(), [ 'account_short' => '测试账户', 'account_type' => '网商银行', ]) ->assertOk() ->assertJsonPath('account_last4', '5678'); $this->assertDatabaseHas('fin_accounts', [ 'account_short' => '测试账户', 'account_last4' => '5678', ], 'business_mirror'); $this->loginAs()->postJson($this->typeUrl(), [ 'account_short' => '测试账户', 'account_type' => '支付宝', ]) ->assertOk() ->assertJsonPath('account_last4', '/'); $this->assertDatabaseHas('fin_accounts', [ 'account_short' => '测试账户', 'account_last4' => '/', ], 'business_mirror'); $this->loginAs()->get($this->pageUrl()) ->assertOk() ->assertSee('/', false); } public function test_account_status_can_be_saved_and_updated_without_duplicate_rows(): void { $payload = [ 'account_short' => '测试账户', 'status' => 'limited', ]; $this->loginAs()->postJson($this->statusUrl(), $payload) ->assertOk() ->assertJsonPath('status', 'limited') ->assertJsonPath('label', '限额'); $this->assertDatabaseHas('fin_accounts', [ 'account_short' => '测试账户', 'current_balance' => 321.45, 'daily_available' => 0, 'blocked_amount' => 321.45, ], 'business_mirror'); $this->loginAs()->postJson($this->statusUrl(), [ 'account_short' => '测试账户', 'status' => 'normal', ])->assertOk()->assertJsonPath('label', '正常'); $this->assertDatabaseHas('fin_accounts', [ 'account_short' => '测试账户', 'current_balance' => 321.45, 'daily_available' => 321.45, 'blocked_amount' => 0, ], 'business_mirror'); $this->assertDatabaseCount('fin_account_statuses', 1, 'business_mirror'); $this->assertDatabaseHas('fin_account_statuses', [ 'account_short' => '测试账户', 'status' => 'normal', 'updated_by' => 'Test User', ], 'business_mirror'); $this->loginAs()->get($this->pageUrl()) ->assertOk() ->assertSee('value="normal" selected', false); } public function test_company_plan_and_account_keep_can_be_updated(): void { DB::connection('business_mirror')->table('fin_accounts')->insert([ 'company_name' => '规划选项公司', 'account_short' => '规划选项账户', 'company_plan' => '待注销', ]); $this->loginAs()->postJson($this->planUrl(), [ 'account_short' => '测试账户', 'company_plan' => '待注销', ])->assertOk()->assertJsonPath('label', '待注销'); $this->loginAs()->postJson($this->keepUrl(), [ 'account_short' => '测试账户', 'account_should_keep' => '否', ])->assertOk()->assertJsonPath('label', '否'); $this->assertDatabaseHas('fin_accounts', [ 'account_short' => '测试账户', 'company_plan' => '待注销', 'account_should_keep' => '否', ], 'business_mirror'); $this->withExceptionHandling()->loginAs()->postJson($this->planUrl(), [ 'account_short' => '测试账户', 'company_plan' => '不存在的规划', ])->assertUnprocessable()->assertJsonPath('message', '请选择现有公司规划。'); $this->withExceptionHandling()->loginAs()->postJson($this->keepUrl(), [ 'account_short' => '测试账户', 'account_should_keep' => '可能', ])->assertUnprocessable()->assertJsonValidationErrors('account_should_keep'); } public function test_editor_can_create_new_company_and_account_with_opening_balance(): void { $this->loginAs()->postJson($this->createAccountUrl(), [ 'company_name' => '新设财务公司', 'company_register_place' => '上海市浦东新区', 'company_business_type' => '有限责任公司', 'company_tax_type' => '一般纳税人', 'company_status' => '正常', 'company_basic_bank' => '中国农业银行上海浦东支行', 'company_basic_account_last4' => '1234', 'company_remark' => '新设公司财务备注', 'account_short' => '新设财务公司农行', 'account_type' => '公户', 'status' => 'limited', 'region' => '上海', 'account_no' => '6222000000001234', 'bank_name' => '中国农业银行', 'bank_city_branch' => '上海浦东支行', 'company_plan' => '待注销', 'account_should_keep' => '否', 'opening_date' => '2026-10-20', 'opening_balance' => '12345.67', ]) ->assertCreated() ->assertJsonPath('account.company_name', '新设财务公司') ->assertJsonPath('account.account_short', '新设财务公司农行') ->assertJsonPath('account.company_created', true) ->assertJsonPath('account.status', 'limited') ->assertJsonPath('message', '已新增公司并建立首个账户。'); $this->assertDatabaseHas('fin_companies', [ 'name' => '新设财务公司', 'register_place' => '上海市浦东新区', 'business_type' => '有限责任公司', 'tax_type' => '一般纳税人', 'status' => '正常', 'basic_bank' => '中国农业银行上海浦东支行', 'basic_account_last4' => '1234', 'remark' => '新设公司财务备注', ], 'business_mirror'); $this->assertDatabaseHas('fin_accounts', [ 'company_name' => '新设财务公司', 'account_short' => '新设财务公司农行', 'account_type' => '公户', 'region' => '上海', 'account_no' => '6222000000001234', 'account_last4' => '1234', 'company_plan' => '待注销', 'account_should_keep' => '否', 'current_balance' => 12345.67, 'daily_available' => 0, 'blocked_amount' => 12345.67, ], 'business_mirror'); $this->assertDatabaseHas('fin_account_statuses', [ 'account_short' => '新设财务公司农行', 'status' => 'limited', 'updated_by' => 'Test User', ], 'business_mirror'); $this->assertDatabaseHas('fin_account_opening', [ 'account_short' => '新设财务公司农行', 'opening_date' => '2026-10-20', 'opening_balance' => 12345.67, ], 'business_mirror'); $this->assertDatabaseHas('fin_daily_flows', [ 'company_name' => '新设财务公司', 'account_short' => '新设财务公司农行', 'flow_date' => '2026-10-20', 'opening_balance' => 12345.67, 'closing_balance' => 12345.67, ], 'business_mirror'); $this->loginAs()->get($this->pageUrl() . '?accounts_short=' . urlencode('新设财务公司农行')) ->assertOk() ->assertSee('新设财务公司') ->assertSee('新设财务公司农行') ->assertSee('>1.23', false); $this->withExceptionHandling()->loginAs()->postJson($this->createAccountUrl(), [ 'company_name' => '另一个公司', 'account_short' => '新设财务公司农行', 'status' => 'normal', 'opening_date' => '2026-10-20', 'opening_balance' => 0, ])->assertUnprocessable()->assertJsonValidationErrors('account_short'); $this->assertDatabaseCount('fin_companies', 1, 'business_mirror'); } public function test_existing_company_profile_can_be_updated_with_a_new_account(): void { DB::connection('business_mirror')->table('fin_companies')->insert([ 'name' => '已有资料公司', 'register_place' => '江苏省苏州市', 'business_type' => '个体工商户', 'tax_type' => '小规模纳税人', 'status' => '存续', 'remark' => '原公司备注', ]); $this->loginAs()->postJson($this->createAccountUrl(), [ 'company_name' => '已有资料公司', 'company_business_type' => '有限责任公司', 'company_tax_type' => '一般纳税人', 'company_status' => '正常', 'company_basic_bank' => '中国工商银行苏州分行', 'company_basic_account_last4' => '8899', 'company_remark' => '更新后的公司备注', 'account_short' => '已有资料公司工行', 'account_type' => '公户', 'status' => 'normal', 'account_no' => '1102000000008899', 'opening_date' => '2026-10-20', 'opening_balance' => 0, ]) ->assertCreated() ->assertJsonPath('account.company_created', false) ->assertJsonPath('account.company_updated', true) ->assertJsonPath('message', '已新增账户并更新公司资料。'); $this->assertDatabaseHas('fin_companies', [ 'name' => '已有资料公司', 'register_place' => '江苏省苏州市', 'business_type' => '有限责任公司', 'tax_type' => '一般纳税人', 'status' => '正常', 'basic_bank' => '中国工商银行苏州分行', 'basic_account_last4' => '8899', 'remark' => '更新后的公司备注', ], 'business_mirror'); } public function test_invalid_status_or_account_is_rejected(): void { $this->withExceptionHandling()->loginAs()->postJson($this->statusUrl(), [ 'account_short' => '测试账户', 'status' => 'unknown', ])->assertUnprocessable()->assertJsonValidationErrors('status'); $this->withExceptionHandling()->loginAs()->postJson($this->statusUrl(), [ 'account_short' => '不存在的账户', 'status' => 'limited', ])->assertUnprocessable()->assertJsonPath('message', '账户简称不存在,请刷新账户总表后重试。'); $this->assertDatabaseCount('fin_account_statuses', 0, 'business_mirror'); } public function test_user_without_finance_editor_role_cannot_change_account_status(): void { $viewer = user_model_class()::create([ 'name' => '只读测试用户', 'email' => 'ledger-viewer-' . uniqid() . '@example.com', 'password' => 'local-test-password', 'locale' => 'zh-CN', 'enabled' => 1, 'landing_page' => 'dashboard', 'created_from' => 'core::test', ]); $viewer->companies()->attach($this->company->id); $viewer->roles()->attach(Role::where('name', 'user')->firstOrFail()->id); $viewer->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs($viewer)->get($this->pageUrl()) ->assertOk() ->assertSee('data-ledger-edit-mode-toggle', false) ->assertSee('当前账号没有财务台账编辑权限') ->assertDontSee('data-account-status data-account-short', false) ->assertDontSee('data-account-type data-account-short', false) ->assertDontSee('class="ledger-btn ledger-add-company"', false); $this->withExceptionHandling()->loginAs($viewer)->postJson($this->statusUrl(), [ 'account_short' => '测试账户', 'status' => 'limited', ])->assertForbidden(); $this->withExceptionHandling()->loginAs($viewer)->postJson($this->planUrl(), [ 'account_short' => '测试账户', 'company_plan' => '保留', ])->assertForbidden(); $this->withExceptionHandling()->loginAs($viewer)->postJson($this->keepUrl(), [ 'account_short' => '测试账户', 'account_should_keep' => '否', ])->assertForbidden(); $this->withExceptionHandling()->loginAs($viewer)->postJson($this->createAccountUrl(), [ 'company_name' => '无权限公司', 'account_short' => '无权限账户', 'status' => 'normal', 'opening_date' => '2026-10-20', 'opening_balance' => 0, ])->assertForbidden(); $this->withExceptionHandling()->loginAs($viewer)->post($this->accountTodoUrl(), [ 'account_short' => '测试账户', 'current_progress' => '处理中', ])->assertForbidden(); $this->assertDatabaseCount('fin_account_statuses', 0, 'business_mirror'); $this->assertDatabaseCount('fin_account_todos', 0, 'business_mirror'); } }