CaiWuXiTong/tests/Feature/Business/FinanceLedgerAccountStatusTest.php

625 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use App\Models\Auth\Role;
use Illuminate\Support\Facades\DB;
use Tests\Feature\FeatureTestCase;
class FinanceLedgerAccountStatusTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->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' => '保留',
]);
}
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,
]);
}
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('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('<th>账户简称</th><th>账户类型</th><th class="ledger-status-column">账户状态</th>', false)
->assertSee('<th class="ledger-plan-column">公司规划</th><th class="ledger-keep-column">账户是否保留</th><th>后4位</th>', 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('1 / 1', false)
->assertSee('data-ledger-column-picker', false)
->assertSee('data-ledger-column-panel', false)
->assertSee('data-ledger-edit-mode-toggle', false)
->assertSee('aria-checked="false"', false)
->assertSee('ledger-account-editor', false)
->assertSee('data-account-edit-display', false)
->assertSee('列设置')
->assertSee('拖动字段可调整列顺序')
->assertSee('拖动表头右侧边界可调整列宽')
->assertSee('恢复默认')
->assertSee('可用金额合计')
->assertSee('不可用金额合计')
->assertSee('href="' . $this->unavailableBalancesUrl() . '"', false)
->assertSee('查看账户异常待办')
->assertSee('可用金额 − 不可用金额')
->assertSee('未填写')
->assertDontSee('data-account-filter-state', false)
->assertSee('value="normal" selected', 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' => 25.5,
'daily_available' => 74.5,
'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' => 50,
'daily_available' => 50,
],
[
'company_name' => '待办测试公司二',
'account_short' => '待办测试账户二',
'account_no' => '6222000000000100',
'account_type' => '个人卡',
'bank_name' => '待办测试银行二',
'bank_city_branch' => '杭州 / 待办测试支行',
'current_balance' => 100,
'blocked_amount' => 10,
'daily_available' => 90,
],
];
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('35.50')
->assertSee('限额')
->assertSee('网商银行')
->assertSee('6222000000000099')
->assertSee('待办测试银行')
->assertSee('上海 / 待办测试支行')
->assertSee('异常原因')
->assertSee('办理方式')
->assertSee('当前进度')
->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)
->assertDontSee('data-pending-account="待办测试账户二"', 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->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('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_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->loginAs()->postJson($this->statusUrl(), [
'account_short' => '测试账户',
'status' => 'normal',
])->assertOk()->assertJsonPath('label', '正常');
$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_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);
$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)->post($this->accountTodoUrl(), [
'account_short' => '测试账户',
'current_progress' => '处理中',
])->assertForbidden();
$this->assertDatabaseCount('fin_account_statuses', 0, 'business_mirror');
$this->assertDatabaseCount('fin_account_todos', 0, 'business_mirror');
}
}