CaiWuXiTong/tests/Feature/Business/FinanceLedgerHeaderControlsTest.php

366 lines
15 KiB
PHP

<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Tests\Feature\FeatureTestCase;
class FinanceLedgerHeaderControlsTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
Carbon::setTestNow('2026-10-18 12:00:00');
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
$connection = DB::connection('business_mirror');
$connection->table('fin_accounts')->insert([
[
'company_name' => '甲公司',
'account_short' => '账户甲',
'account_type' => '公户',
'region' => '上海',
'company_plan' => '保留',
'account_should_keep' => '是',
'current_balance' => 100,
'daily_available' => 80,
'blocked_amount' => 20,
],
[
'company_name' => '乙公司',
'account_short' => '账户乙',
'account_type' => '支付宝',
'region' => '杭州',
'company_plan' => '待注销',
'account_should_keep' => '否',
'current_balance' => 300,
'daily_available' => 300,
'blocked_amount' => 0,
],
]);
$connection->table('fin_shops')->insert([
[
'shop_name' => '天猫店铺',
'platform' => '天猫',
'company_name' => '甲公司',
'account_short' => '账户甲',
'status' => '在营',
'shop_balance' => 100,
],
[
'shop_name' => '拼多多店铺',
'platform' => '拼多多',
'company_name' => '乙公司',
'account_short' => '账户乙',
'status' => '停业',
'shop_balance' => 300,
],
]);
$connection->table('fin_daily_flows')->insert([
[
'flow_date' => '2026-10-17',
'company_name' => '甲公司',
'account_short' => '账户甲',
'opening_balance' => 100,
'inflow' => 10,
'outflow' => 0,
'closing_balance' => 110,
],
[
'flow_date' => '2026-10-18',
'company_name' => '乙公司',
'account_short' => '账户乙',
'opening_balance' => 300,
'inflow' => 0,
'outflow' => 20,
'closing_balance' => 280,
],
]);
$connection->table('fin_transactions')->insert([
[
'txn_date' => '2026-10-17',
'account_short' => '账户甲',
'detail' => '甲笔流水',
'income' => 10,
'expense' => 0,
'approval_no' => 'APR-甲',
'source' => 'dingtalk',
'source_value' => 'VAL-甲',
'process_type' => '报销',
'row_key' => 'header-filter-row-a',
],
[
'txn_date' => '2026-10-18',
'account_short' => '账户乙',
'detail' => '乙笔流水',
'income' => 0,
'expense' => 20,
'approval_no' => 'APR-乙',
'source' => 'shop',
'source_value' => 'VAL-乙',
'process_type' => '提现',
'row_key' => 'header-filter-row-b',
],
]);
$connection->table('fin_account_map')->insert([
[
'source' => 'dingtalk',
'source_value' => 'VAL-甲',
'account_short' => '账户甲',
'txn_count' => 1,
'income_sum' => 10,
'expense_sum' => 0,
],
[
'source' => 'shop',
'source_value' => 'VAL-乙',
'account_short' => null,
'txn_count' => 1,
'income_sum' => 0,
'expense_sum' => 20,
],
]);
}
protected function tearDown(): void
{
Carbon::setTestNow();
parent::tearDown();
}
public function test_every_financial_ledger_table_has_a_header_menu(): void
{
$response = $this->loginAs()->get($this->pageUrl());
$html = $response->assertOk()->getContent();
$this->assertSame(71, preg_match_all('/<details\b[^>]*data-ledger-header-details/', $html));
$this->assertSame(71, preg_match_all('/<span\b[^>]*data-ledger-header-label/', $html));
$this->assertSame(2, preg_match_all('/<select name="transactions_filter_value"/', $html));
$this->assertSame(1, preg_match_all('/<input\b[^>]*name="transactions_filter_value"[^>]*list="ledger-header-options-transactions-effective_account_short"/', $html));
$this->assertStringContainsString(
'<datalist id="ledger-header-options-transactions-effective_account_short">',
$html
);
$this->assertStringContainsString('<option value="账户甲"', $html);
$this->assertStringContainsString('<option value="2026-10-18"', $html);
$this->assertStringContainsString('data-transaction-column-storage-key', $html);
foreach (['accounts', 'shops', 'flows', 'transactions', 'mapping', 'sources'] as $tab) {
$this->assertStringContainsString('name="' . $tab . '_filter_value"', $html);
$this->assertStringContainsString($tab . '_sort=', $html);
}
}
public function test_shop_ledger_header_labels_and_column_order(): void
{
$html = $this->loginAs()->get($this->pageUrl())->assertOk()->getContent();
$panelStart = strpos($html, 'id="ledger-panel-shops"');
$theadStart = strpos($html, '<thead>', $panelStart);
$theadEnd = strpos($html, '</thead>', $theadStart);
$this->assertNotFalse($panelStart);
$this->assertNotFalse($theadStart);
$this->assertNotFalse($theadEnd);
$headerHtml = substr($html, $theadStart, $theadEnd - $theadStart);
$platformPosition = strpos($headerHtml, 'data-ledger-header-field="platform"');
$shopNamePosition = strpos($headerHtml, 'data-ledger-header-field="shop_name"');
$this->assertNotFalse($platformPosition);
$this->assertNotFalse($shopNamePosition);
$this->assertLessThan($shopNamePosition, $platformPosition);
$this->assertStringContainsString('data-ledger-header-label>平台</span>', $headerHtml);
$this->assertStringContainsString('data-ledger-header-label>ERP系统店铺名称</span>', $headerHtml);
$this->assertStringContainsString('data-ledger-header-label>店铺主体</span>', $headerHtml);
$this->assertStringContainsString('data-ledger-header-label>店铺负责人</span>', $headerHtml);
$this->assertStringContainsString('data-ledger-header-label>部门负责人</span>', $headerHtml);
$this->assertStringContainsString('data-ledger-header-label>余额来源</span>', $headerHtml);
$this->assertStringContainsString('data-ledger-header-label>预留标准</span>', $headerHtml);
$this->assertStringContainsString('data-ledger-header-label>余额预警</span>', $headerHtml);
$this->assertStringNotContainsString('data-ledger-header-label>店铺名称</span>', $headerHtml);
$this->assertStringNotContainsString('data-ledger-header-label>所属公司</span>', $headerHtml);
}
public function test_transaction_account_filter_provides_typeahead_suggestions(): void
{
$html = $this->loginAs()->get($this->pageUrl())
->assertOk()
->assertSee('data-ledger-account-filter', false)
->assertSee('data-limit="60"', false)
->assertSee('id="txns-account"', false)
->assertSee('role="combobox"', false)
->assertSee('aria-autocomplete="list"', false)
->assertSee('aria-controls="txns-account-menu"', false)
->assertSee('aria-haspopup="listbox"', false)
->assertSee('data-ledger-account-filter-toggle', false)
->assertSee('data-ledger-account-filter-menu', false)
->assertSee('max-height:min(18rem, 50vh)', false)
->assertSee("input.addEventListener('input'", false)
->assertDontSee('id="txns-account" name="txns_account" list=', false)
->getContent();
$this->assertStringContainsString('<option value="账户甲"', $html);
$this->assertStringContainsString('<option value="账户乙"', $html);
}
public function test_account_and_flow_headers_filter_and_sort_the_full_dataset(): void
{
$filtered = $this->loginAs()->get($this->pageUrl([
'accounts_filter_column' => 'company_name',
'accounts_filter_value' => '乙公司',
]))->assertOk();
$filtered->assertSee('data-account-short="账户乙"', false)
->assertDontSee('data-account-short="账户甲"', false);
$sortedAccounts = $this->loginAs()->get($this->pageUrl([
'accounts_sort' => 'current_balance',
'accounts_direction' => 'desc',
]))->assertOk()->getContent();
$this->assertLessThan(
strpos($sortedAccounts, 'data-account-short="账户甲"'),
strpos($sortedAccounts, 'data-account-short="账户乙"'),
);
$flows = $this->loginAs()->get($this->pageUrl([
'accounts_filter_column' => 'company_name',
'accounts_filter_value' => '乙公司',
'shops_filter_column' => 'company_name',
'shops_filter_value' => '乙公司',
'flows_filter_column' => 'account_short',
'flows_filter_value' => '账户乙',
]))->assertOk();
$flows->assertSee('>乙公司</td>', false)
->assertDontSee('>甲公司</td>', false);
}
public function test_shop_transaction_mapping_and_source_headers_filter_their_tables(): void
{
$this->loginAs()->get($this->pageUrl([
'shops_filter_column' => 'platform',
'shops_filter_value' => '天猫',
]))
->assertOk()
->assertSee('>天猫店铺</td>', false)
->assertDontSee('>拼多多店铺</td>', false);
$this->loginAs()->get($this->pageUrl([
'transactions_filter_column' => 'approval_no',
'transactions_filter_value' => 'APR-乙',
'transactions_sort' => 'expense',
'transactions_direction' => 'desc',
]))
->assertOk()
->assertSee('APR-乙')
->assertDontSee('APR-甲');
$this->loginAs()->get($this->pageUrl([
'transactions_filter_column' => 'effective_account_short',
'transactions_filter_value' => '乙',
]))
->assertOk()
->assertSee('乙笔流水')
->assertDontSee('甲笔流水');
$this->loginAs()->get($this->pageUrl([
'mapping_filter_column' => 'mapping_status',
'mapping_filter_value' => 'unmapped',
]))
->assertOk()
->assertSee('VAL-乙')
->assertDontSee('VAL-甲');
$this->loginAs()->get($this->pageUrl([
'sources_filter_column' => 'source',
'sources_filter_value' => 'shop',
]))
->assertOk()
->assertSee('>提现</td>', false)
->assertDontSee('>报销</td>', false);
}
public function test_ledger_pages_default_to_the_current_month_and_allow_history_selection(): void
{
Carbon::setTestNow('2026-11-05 12:00:00');
$connection = DB::connection('business_mirror');
$connection->table('fin_daily_flows')->insert([
'flow_date' => '2026-11-02',
'company_name' => '甲公司',
'account_short' => '账户甲',
'opening_balance' => 100,
'inflow' => 30,
'outflow' => 0,
'closing_balance' => 130,
'remark' => '11月流水行',
]);
$connection->table('fin_transactions')->insert([
'txn_date' => '2026-11-02',
'account_short' => '账户甲',
'detail' => '11月动账明细',
'income' => 30,
'expense' => 0,
'approval_no' => 'APR-11',
'source' => 'dingtalk',
'source_value' => 'VAL-11',
'process_type' => '11月报销',
'row_key' => 'current-month-row-11',
]);
$connection->table('fin_account_map')->insert([
'source' => 'dingtalk',
'source_value' => 'VAL-11',
'account_short' => '账户甲',
'txn_count' => 1,
'income_sum' => 30,
'expense_sum' => 0,
]);
$this->loginAs()->get($this->pageUrl())
->assertOk()
->assertSee('name="flows_from" value="2026-11-01"', false)
->assertSee('name="flows_to" value="2026-11-30"', false)
->assertSee('name="txns_from" value="2026-11-01"', false)
->assertSee('name="txns_to" value="2026-11-30"', false)
->assertSee('name="mapping_from" value="2026-11-01"', false)
->assertSee('name="mapping_to" value="2026-11-30"', false)
->assertSee('name="sources_from" value="2026-11-01"', false)
->assertSee('name="sources_to" value="2026-11-30"', false)
->assertSee('11月流水行')
->assertSee('11月动账明细')
->assertSee('VAL-11')
->assertSee('11月报销')
->assertDontSee('甲笔流水')
->assertDontSee('VAL-甲')
->assertDontSee('>报销</td>', false);
$this->loginAs()->get($this->pageUrl([
'flows_from' => '2026-10-01',
'flows_to' => '2026-10-31',
'txns_from' => '2026-10-01',
'txns_to' => '2026-10-31',
'mapping_from' => '2026-10-01',
'mapping_to' => '2026-10-31',
'sources_from' => '2026-10-01',
'sources_to' => '2026-10-31',
]))
->assertOk()
->assertSee('甲笔流水')
->assertSee('VAL-甲')
->assertSee('>报销</td>', false)
->assertDontSee('11月动账明细')
->assertDontSee('VAL-11');
}
private function pageUrl(array $parameters = []): string
{
return route('business.pages.show', array_merge([
'company_id' => $this->company->id,
'section' => 'financial-operations',
'page' => 'ledger',
], $parameters));
}
}