user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail(), ); $connection = DB::connection('business_mirror'); $accountId = $connection->table('fin_accounts')->insertGetId([ 'company_name' => '手动编辑测试公司', 'account_short' => '手动编辑测试账户', 'account_type' => '公户', 'account_no' => '6222000000008899', 'current_balance' => 100, 'daily_available' => 100, ]); $this->accountId = (int) $accountId; $connection->table('fin_account_opening')->insert([ 'account_short' => '手动编辑测试账户', 'opening_date' => '2026-10-17', 'opening_balance' => 100, ]); $connection->table('fin_transactions')->insert([ [ 'txn_date' => '2026-10-18', 'account_short' => '手动编辑测试账户', 'detail' => '自动入账', 'income' => 25, 'expense' => 0, 'source' => 'manual', 'process_type' => '手工录入', 'row_key' => hash('sha256', 'manual-edit-income'), ], [ 'txn_date' => '2026-10-19', 'account_short' => '手动编辑测试账户', 'detail' => '自动出账', 'income' => 0, 'expense' => 30, 'source' => 'manual', 'process_type' => '手工录入', 'row_key' => hash('sha256', 'manual-edit-expense'), ], ]); app(FinanceLedgerCalculator::class)->recalculate(); } private int $accountId; public function test_account_and_daily_flow_edit_controls_are_visible(): void { $html = $this->loginAs()->get($this->pageUrl())->assertOk()->getContent(); $this->assertStringContainsString('data-ledger-account-edit', $html); $this->assertStringContainsString('data-ledger-account-dialog', $html); $this->assertStringContainsString('data-ledger-flow-edit', $html); $this->assertStringContainsString('data-ledger-flow-dialog', $html); } public function test_account_editor_updates_fields_renames_references_and_writes_audit(): void { $this->loginAs()->get($this->accountEditUrl()) ->assertOk() ->assertJsonPath('account.id', $this->accountId) ->assertJsonPath('account.account_short', '手动编辑测试账户'); $this->loginAs()->postJson($this->accountUpdateUrl(), [ 'company_name' => '手动编辑测试公司', 'account_short' => '手动编辑测试账户新', 'account_type' => '公户', 'status' => 'limited', 'region' => '苏州', 'account_no' => '6222000000008899', 'bank_name' => '测试银行', 'bank_city_branch' => '苏州分行', 'company_plan' => '待注销', 'account_should_keep' => '否', 'single_limit' => '100000', 'daily_limit' => '200000', 'monthly_limit' => '300000', 'yearly_limit' => '400000', 'clearing_days' => 2.5, 'raise_target' => '500000', 'raise_status' => '处理中', 'raise_actions' => '提升限额', 'done_actions' => '提交资料', 'next_action' => '等待审核', 'remark' => '账户手动修改', 'owner' => '财务负责人', ])->assertOk()->assertJsonPath('account.account_short', '手动编辑测试账户新'); $connection = DB::connection('business_mirror'); $this->assertDatabaseHas('fin_accounts', [ 'id' => $this->accountId, 'account_short' => '手动编辑测试账户新', 'region' => '苏州', 'bank_name' => '测试银行', 'owner' => '财务负责人', ], 'business_mirror'); foreach (['fin_account_opening', 'fin_daily_flows', 'fin_transactions', 'fin_shops'] as $table) { $this->assertSame( 0, $connection->table($table)->where('account_short', '手动编辑测试账户')->count(), $table . ' 仍保留旧账户简称', ); } $this->assertDatabaseHas('fin_account_statuses', [ 'account_short' => '手动编辑测试账户新', 'status' => 'limited', ], 'business_mirror'); $this->assertDatabaseHas('fin_ledger_edit_events', [ 'entity_type' => 'account', 'entity_key' => (string) $this->accountId, 'user_name' => 'Test User', ], 'business_mirror'); } public function test_daily_flow_override_cascades_and_can_reset_to_automatic(): void { $this->loginAs()->get($this->flowEditUrl('2026-10-18')) ->assertOk() ->assertJsonPath('flow.opening_balance', 100) ->assertJsonPath('flow.inflow', 25) ->assertJsonPath('flow.closing_balance', 125); $this->loginAs()->postJson($this->flowUpdateUrl(), [ 'flow_date' => '2026-10-18', 'account_short' => '手动编辑测试账户', 'opening_balance' => 100, 'inflow' => 50, 'outflow' => 10, 'closing_balance' => 140, 'remark' => '银行流水手工校正', ])->assertOk() ->assertJsonPath('flow.manual_override', true) ->assertJsonPath('flow.closing_balance', 140); $connection = DB::connection('business_mirror'); $this->assertDatabaseHas('fin_daily_flows', [ 'flow_date' => '2026-10-18', 'account_short' => '手动编辑测试账户', 'inflow' => 50, 'outflow' => 10, 'closing_balance' => 140, 'manual_override' => 1, 'updated_by' => 'Test User', ], 'business_mirror'); $this->assertDatabaseHas('fin_daily_flows', [ 'flow_date' => '2026-10-19', 'account_short' => '手动编辑测试账户', 'opening_balance' => 140, 'outflow' => 30, 'closing_balance' => 110, ], 'business_mirror'); $this->assertSame(110.0, (float) $connection->table('fin_accounts') ->where('id', $this->accountId) ->value('current_balance')); $this->loginAs()->postJson($this->flowResetUrl(), [ 'flow_date' => '2026-10-18', 'account_short' => '手动编辑测试账户', ])->assertOk()->assertJsonPath('flow.manual_override', false); $this->assertDatabaseMissing('fin_daily_flow_overrides', [ 'flow_date' => '2026-10-18', 'account_short' => '手动编辑测试账户', ], 'business_mirror'); $this->assertDatabaseHas('fin_daily_flows', [ 'flow_date' => '2026-10-19', 'account_short' => '手动编辑测试账户', 'opening_balance' => 125, 'closing_balance' => 95, ], 'business_mirror'); } public function test_manual_edit_validates_equation_duplicate_short_and_permission(): void { $this->withExceptionHandling()->loginAs()->postJson($this->flowUpdateUrl(), [ 'flow_date' => '2026-10-18', 'account_short' => '手动编辑测试账户', 'opening_balance' => 100, 'inflow' => 10, 'outflow' => 5, 'closing_balance' => 120, ])->assertUnprocessable()->assertJsonValidationErrors('closing_balance'); $connection = DB::connection('business_mirror'); $connection->table('fin_accounts')->insert([ 'company_name' => '重名公司', 'account_short' => '已存在账户简称', ]); $this->withExceptionHandling()->loginAs()->postJson($this->accountUpdateUrl(), [ 'company_name' => '手动编辑测试公司', 'account_short' => '已存在账户简称', 'status' => 'normal', ])->assertUnprocessable()->assertJsonValidationErrors('account_short'); $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->withExceptionHandling()->loginAs($viewer)->postJson($this->flowUpdateUrl(), [ 'flow_date' => '2026-10-18', 'account_short' => '手动编辑测试账户', 'opening_balance' => 100, 'inflow' => 10, 'outflow' => 5, 'closing_balance' => 105, ])->assertForbidden(); } private function pageUrl(): string { return route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'financial-operations', 'page' => 'ledger', ]) . '?tab=flows'; } private function accountEditUrl(): string { return route('business.financial-ledger.accounts.edit', [ 'company_id' => $this->company->id, 'id' => $this->accountId, ]); } private function accountUpdateUrl(): string { return route('business.financial-ledger.accounts.update', [ 'company_id' => $this->company->id, 'id' => $this->accountId, ]); } private function flowEditUrl(string $date): string { return route('business.financial-ledger.flows.edit', [ 'company_id' => $this->company->id, 'flow_date' => $date, 'account_short' => '手动编辑测试账户', ]); } private function flowUpdateUrl(): string { return route('business.financial-ledger.flows.update', ['company_id' => $this->company->id]); } private function flowResetUrl(): string { return route('business.financial-ledger.flows.reset', ['company_id' => $this->company->id]); } }