282 lines
11 KiB
PHP
282 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Business;
|
|
|
|
use App\Models\Auth\Permission;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tests\Feature\FeatureTestCase;
|
|
|
|
class FinanceLedgerHeaderControlsTest extends FeatureTestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$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,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function test_every_financial_ledger_table_has_a_header_menu(): void
|
|
{
|
|
$response = $this->loginAs()->get($this->pageUrl());
|
|
$html = $response->assertOk()->getContent();
|
|
|
|
$this->assertSame(63, preg_match_all('/<details\b[^>]*data-ledger-header-details/', $html));
|
|
$this->assertSame(63, 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->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);
|
|
}
|
|
|
|
private function pageUrl(array $parameters = []): string
|
|
{
|
|
return route('business.pages.show', array_merge([
|
|
'company_id' => $this->company->id,
|
|
'section' => 'financial-operations',
|
|
'page' => 'ledger',
|
|
], $parameters));
|
|
}
|
|
}
|