CaiWuXiTong/tests/Feature/Business/FinanceLedgerAccountStatusTest.php

956 lines
40 KiB
PHP

<?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' => '保留',
'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</td>', false)
->assertSee('>12.00</td>', false)
->assertSee('>0.35</td>', false)
->assertSee('>20.00 / 1.00</td>', false)
->assertSee('>5.00</td>', false)
->assertSee('<td class="center" title="200000/10000">20.00 / 1.00</td>', false)
->assertSee('<td class="center" title="300,000.00 元">30.00</td>', false)
->assertSee('<td class="center" title="400,000.00 元">40.00</td>', false)
->assertSee('<td class="center" title="500,000.00 元">50.00</td>', 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('<th data-pending-column-key="current_balance">当前余额</th>', 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('<td class="center" data-account-last4>/</td>', 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</td>', 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');
}
}