user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); } private function ledgerUrl(): string { return route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', ]); } private function keepTasksUrl(array $parameters = []): string { return route('business.financial-ledger.unretained-accounts', array_merge([ 'company_id' => $this->company->id, ], $parameters)); } private function keepTodoUrl(): string { return route('business.financial-ledger.accounts.keep-todo', [ 'company_id' => $this->company->id, ]); } public function test_ledger_card_follows_account_count_and_links_to_independent_keep_tasks(): void { DB::connection('business_mirror')->table('fin_accounts')->insert([ [ 'company_name' => '不保留测试公司甲', 'account_short' => '显式不保留账户', 'company_plan' => '保留', 'account_should_keep' => '否', ], [ 'company_name' => '不保留测试公司乙', 'account_short' => '规划不保留账户', 'company_plan' => '待注销', 'account_should_keep' => null, ], [ 'company_name' => '不保留测试公司丙', 'account_short' => '明确保留账户', 'company_plan' => '待注销', 'account_should_keep' => '是', ], [ 'company_name' => '不保留测试公司丁', 'account_short' => '规划保留账户', 'company_plan' => '保留', 'account_should_keep' => null, ], ]); $response = $this->loginAs()->get($this->ledgerUrl())->assertOk(); $content = $response->getContent(); $this->assertMatchesRegularExpression( '/账户数<\/span>4<\/b><\/div>\s*]+aria-label="查看不保留账户处理待办"[^>]*>不保留账户数<\/span>2<\/b><\/a>/u', $content, ); $response->assertSee('href="' . $this->keepTasksUrl() . '"', false); } public function test_keep_tasks_page_uses_required_columns_filters_and_retention_rule(): void { $connection = DB::connection('business_mirror'); $connection->table('fin_accounts')->insert([ [ 'company_name' => '展示测试公司甲', 'account_short' => '显式不保留账户', 'account_type' => '公户', 'account_no' => '6222000000000001', 'bank_name' => '展示测试银行甲', 'bank_city_branch' => '上海 / 测试支行', 'company_plan' => '保留', 'account_should_keep' => '否', ], [ 'company_name' => '展示测试公司乙', 'account_short' => '规划不保留账户', 'account_type' => '个人卡', 'account_no' => null, 'bank_name' => null, 'bank_city_branch' => null, 'company_plan' => '待注销', 'account_should_keep' => null, ], [ 'company_name' => '展示测试公司丙', 'account_short' => '明确保留账户', 'account_type' => '支付宝', 'account_no' => null, 'bank_name' => null, 'bank_city_branch' => null, 'company_plan' => '待注销', 'account_should_keep' => '是', ], [ 'company_name' => '展示测试公司丁', 'account_short' => '规划保留账户', 'account_type' => '网商银行', 'account_no' => null, 'bank_name' => null, 'bank_city_branch' => null, 'company_plan' => '保留', 'account_should_keep' => null, ], ]); $connection->table('fin_account_statuses')->insert([ 'account_short' => '显式不保留账户', 'status' => 'limited', 'updated_by' => 'Test User', ]); $response = $this->loginAs()->get($this->keepTasksUrl())->assertOk(); $content = $response->getContent(); $response ->assertSee('不保留账户处理待办') ->assertSee('不与其他页面的待办事项合并') ->assertSee('data-keep-account="显式不保留账户"', false) ->assertSee('data-keep-account="规划不保留账户"', false) ->assertDontSee('data-keep-account="明确保留账户"', false) ->assertDontSee('data-keep-account="规划保留账户"', false) ->assertSee('name="keep_short"', false) ->assertSee('name="keep_type"', false) ->assertSee('name="keep_status"', false) ->assertSee('name="keep_method"', false) ->assertSee('name="keep_owner"', false) ->assertSee('name="keep_progress"', false) ->assertSee('按开始时间排序') ->assertSee('按完成时间排序') ->assertSee('线上') ->assertSee('线下') ->assertSee('已完成') ->assertSee('进行中') ->assertSee('待开始') ->assertSee('data-keep-tbody', false) ->assertSee('data-keep-pagination', false) ->assertSee('拖动表头右侧边界可调整列宽') ->assertSee('cursor:col-resize', false) ->assertSee('position:sticky', false) ->assertSee('currentBody.replaceWith', false) ->assertSee('history.replaceState', false) ->assertSee('nextWrap.scrollTop = scrollTop', false); preg_match_all('/assertSame([ 'company_name', 'account_short', 'account_type', 'account_status', 'account_no', 'bank_name', 'bank_city_branch', 'account_should_keep', 'handling_method', 'owner_name', 'started_at', 'completed_at', 'current_progress', ], $matches[1]); } public function test_keep_tasks_filters_sort_and_ajax_pagination_work(): void { $connection = DB::connection('business_mirror'); $connection->table('fin_accounts')->insert([ [ 'company_name' => '筛选公司甲', 'account_short' => '筛选账户甲', 'account_type' => '公户', 'company_plan' => '保留', 'account_should_keep' => '否', ], [ 'company_name' => '筛选公司乙', 'account_short' => '筛选账户乙', 'account_type' => '个人卡', 'company_plan' => '待注销', 'account_should_keep' => null, ], ]); $connection->table('fin_account_statuses')->insert([ 'account_short' => '筛选账户甲', 'status' => 'limited', 'updated_by' => 'Test User', ]); $connection->table('fin_account_keep_todos')->insert([ [ 'account_short' => '筛选账户甲', 'handling_method' => 'online', 'owner_name' => '甲负责人', 'started_at' => '2026-10-21 09:00:00', 'current_progress' => 'in_progress', ], [ 'account_short' => '筛选账户乙', 'handling_method' => 'offline', 'owner_name' => '乙负责人', 'started_at' => '2026-10-22 09:00:00', 'current_progress' => 'not_started', ], ]); $this->loginAs()->get($this->keepTasksUrl([ 'keep_short' => '筛选账户甲', 'keep_type' => '公户', 'keep_status' => 'limited', 'keep_method' => 'online', 'keep_owner' => '甲负责人', 'keep_progress' => 'in_progress', ])) ->assertOk() ->assertSee('data-keep-account="筛选账户甲"', false) ->assertDontSee('data-keep-account="筛选账户乙"', false) ->assertSee('value="limited" selected', false) ->assertSee('value="online" selected', false) ->assertSee('value="in_progress" selected', false); $sortResponse = $this->loginAs()->get($this->keepTasksUrl([ 'keep_sort' => 'started_at', 'keep_direction' => 'desc', ]))->assertOk()->assertSee('aria-sort="descending"', false); preg_match_all('/data-keep-account="([^"]+)"/u', $sortResponse->getContent(), $sortedAccounts); $this->assertSame(['筛选账户乙', '筛选账户甲'], $sortedAccounts[1]); $pageRows = []; for ($index = 1; $index <= 51; $index++) { $pageRows[] = [ 'company_name' => '分页公司', 'account_short' => 'keep-page-' . str_pad((string) $index, 2, '0', STR_PAD_LEFT), 'account_should_keep' => '否', ]; } $connection->table('fin_accounts')->insert($pageRows); $this->loginAs()->get($this->keepTasksUrl(['keep_short' => 'keep-page-'])) ->assertOk() ->assertSee('1 / 2') ->assertSee('aria-label="下一页"', false) ->assertSee('data-keep-pagination', false); $this->loginAs()->get($this->keepTasksUrl(['keep_short' => 'keep-page-', 'page' => 2])) ->assertOk() ->assertSee('2 / 2') ->assertSee('data-keep-account="keep-page-51"', false); } public function test_keep_todo_save_is_isolated_from_abnormal_tasks_and_permission_protected(): void { $connection = DB::connection('business_mirror'); $connection->table('fin_accounts')->insert([ [ 'company_name' => '待办保存公司甲', 'account_short' => '待办不保留账户', 'company_plan' => '保留', 'account_should_keep' => '否', ], [ 'company_name' => '待办保存公司乙', 'account_short' => '规划不保留待办账户', 'company_plan' => '待注销', 'account_should_keep' => null, ], [ 'company_name' => '待办保存公司丙', 'account_short' => '待办保留账户', 'company_plan' => '保留', 'account_should_keep' => '是', ], ]); $payload = [ 'account_short' => '待办不保留账户', 'handling_method' => 'online', 'owner_name' => '待办负责人', 'started_at' => '2026-10-21T09:00', 'completed_at' => '2026-10-21T11:30', 'current_progress' => 'in_progress', ]; $this->loginAs()->post($this->keepTodoUrl(), $payload)->assertRedirect(); $this->assertDatabaseHas('fin_account_keep_todos', [ 'account_short' => '待办不保留账户', 'handling_method' => 'online', 'owner_name' => '待办负责人', 'current_progress' => 'in_progress', 'updated_by' => 'Test User', ], 'business_mirror'); $this->assertDatabaseCount('fin_account_todos', 0, 'business_mirror'); $this->loginAs()->post($this->keepTodoUrl(), array_merge($payload, [ 'current_progress' => 'completed', ]))->assertRedirect(); $this->assertDatabaseCount('fin_account_keep_todos', 1, 'business_mirror'); $this->assertDatabaseHas('fin_account_keep_todos', [ 'account_short' => '待办不保留账户', 'current_progress' => 'completed', ], 'business_mirror'); $this->loginAs()->post($this->keepTodoUrl(), [ 'account_short' => '规划不保留待办账户', 'current_progress' => 'not_started', ])->assertRedirect(); $this->assertDatabaseHas('fin_account_keep_todos', [ 'account_short' => '规划不保留待办账户', 'current_progress' => 'not_started', ], 'business_mirror'); $this->withExceptionHandling()->loginAs()->post($this->keepTodoUrl(), [ 'account_short' => '待办保留账户', 'current_progress' => 'not_started', ])->assertStatus(422); $viewer = user_model_class()::create([ 'name' => '不保留账户只读用户', 'email' => 'keep-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->keepTasksUrl()) ->assertOk() ->assertSee('disabled', false) ->assertDontSee('title="保存待办"', false); $this->withExceptionHandling()->loginAs($viewer)->post($this->keepTodoUrl(), $payload) ->assertForbidden(); } }