feat: 账户总表增加银行账户与非店铺账户视图 (#1076)

This commit is contained in:
shanshi 2026-11-11 14:30:35 +08:00
parent d512e5d9ba
commit bad13f90c5
3 changed files with 118 additions and 21 deletions

View File

@ -91,12 +91,16 @@ class FinanceLedgerPageData
$flowView = in_array($requestedFlowView, ['summary', 'bank', 'non_store', 'daily'], true)
? $requestedFlowView
: 'summary';
$requestedAccountView = trim((string) request('accounts_view', 'all'));
$accountView = in_array($requestedAccountView, ['all', 'bank', 'non_store'], true)
? $requestedAccountView
: 'all';
$flowPeriod = $this->dateRange('flows');
$transactionPeriod = $this->dateRange('txns');
$mappingPeriod = $this->dateRange('mapping');
$sourcePeriod = $this->dateRange('sources');
$accounts = $this->accounts();
$accounts = $this->accounts($accountView);
$shops = $this->shops();
$availableBreakdown = $this->availableBreakdown($shops);
$flows = $this->flows($flowPeriod, $flowView);
@ -113,6 +117,7 @@ class FinanceLedgerPageData
'ledgerTab' => $tab,
'ledgerTabs' => self::TABS,
'ledgerOpeningDate' => $openingDate,
'ledgerAccountView' => $accountView,
'ledgerAccounts' => $accounts['rows'],
'ledgerAccountSummary' => $accounts['summary'],
'ledgerShops' => $shops['rows'],
@ -155,12 +160,13 @@ class FinanceLedgerPageData
];
}
private function accounts(): array
private function accounts(string $view = 'all'): array
{
$query = FinanceLedgerIndependentAccounts::exclude(
$this->db()->table('fin_accounts as a'),
'a.account_short',
);
$this->applyAccountCategoryScope($query, $view, 'a.account_short', 'a.account_type');
$keyword = trim((string) request('accounts_q', ''));
if ($keyword !== '') {
$query->where(function (Builder $builder) use ($keyword): void {
@ -632,6 +638,30 @@ class FinanceLedgerPageData
];
}
private function applyAccountCategoryScope(
Builder $query,
string $view,
string $accountShortColumn,
string $accountTypeColumn,
): void {
if ($view === 'bank') {
$query->where(function (Builder $scope) use ($accountShortColumn, $accountTypeColumn): void {
$scope->where(function (Builder $account) use ($accountShortColumn): void {
$account->whereNull($accountShortColumn)
->orWhere($accountShortColumn, 'not like', '%支付宝%');
})->where(function (Builder $accountType) use ($accountTypeColumn): void {
$accountType->whereNull($accountTypeColumn)
->orWhere($accountTypeColumn, 'not like', '%支付宝%');
});
});
} elseif ($view === 'non_store') {
$query->where(function (Builder $scope) use ($accountShortColumn, $accountTypeColumn): void {
$scope->where($accountShortColumn, 'like', '%支付宝%')
->orWhere($accountTypeColumn, 'like', '%支付宝%');
});
}
}
private function accountFlowSummary(array $period, string $view = 'summary'): array
{
$customFields = $this->flowSummaryCustomFields();
@ -639,22 +669,7 @@ class FinanceLedgerPageData
$this->db()->table('fin_accounts as a'),
'a.account_short',
);
if ($view === 'bank') {
$accountQuery->where(function ($scope): void {
$scope->where(function ($account): void {
$account->whereNull('a.account_short')
->orWhere('a.account_short', 'not like', '%支付宝%');
})->where(function ($accountType): void {
$accountType->whereNull('a.account_type')
->orWhere('a.account_type', 'not like', '%支付宝%');
});
});
} elseif ($view === 'non_store') {
$accountQuery->where(function ($scope): void {
$scope->where('a.account_short', 'like', '%支付宝%')
->orWhere('a.account_type', 'like', '%支付宝%');
});
}
$this->applyAccountCategoryScope($accountQuery, $view, 'a.account_short', 'a.account_type');
foreach (['flows_company' => 'a.company_name', 'flows_account' => 'a.account_short'] as $param => $column) {
$value = trim((string) request($param, ''));
if ($value !== '') {

View File

@ -62,6 +62,26 @@
$date = static fn ($value): string => $value ? \Illuminate\Support\Carbon::parse($value)->format('Y-m-d') : '';
$ledgerPageUrl = route('business.pages.show', ['section' => 'financial-operations', 'page' => 'ledger']);
$tabUrl = static fn (string $tab): string => $ledgerPageUrl . '?' . http_build_query(['tab' => $tab]);
$accountView = $ledgerAccountView ?? 'all';
$accountViewQuery = collect(request()->query())
->except([
'tab',
'accounts_page',
'accounts_filter_column',
'accounts_filter_value',
'accounts_sort',
'accounts_direction',
])
->filter(static fn ($value): bool => is_scalar($value) || $value === null)
->all();
$accountViewUrl = static fn (string $view): string => $ledgerPageUrl . '?' . http_build_query(array_merge(
$accountViewQuery,
['tab' => 'accounts', 'accounts_view' => $view],
));
$accountViewResetUrl = $ledgerPageUrl . '?' . http_build_query([
'tab' => 'accounts',
'accounts_view' => $accountView,
]);
$transactionView = $ledgerTransactionView ?? 'all';
$transactionViews = $ledgerTransactionViews ?? [];
$transactionViewAccount = $ledgerTransactionViewAccount ?? null;
@ -660,6 +680,7 @@
<section class="ledger-panel" id="ledger-panel-accounts" role="tabpanel" data-ledger-panel="accounts" @if ($activeTab !== 'accounts') hidden @endif>
<form class="ledger-toolbar" method="GET" action="{{ $tabUrl('accounts') }}">
<input type="hidden" name="tab" value="accounts">
<input type="hidden" name="accounts_view" value="{{ $accountView }}">
<div class="ledger-field"><label for="accounts-q">关键词</label><input id="accounts-q" name="accounts_q" value="{{ request('accounts_q') }}" placeholder="公司、账号、责任人"></div>
<div class="ledger-field" style="min-width:11rem"><label for="accounts-short">账户简称</label><input id="accounts-short" name="accounts_short" list="ledger-account-list" value="{{ request('accounts_short') }}" placeholder="选择或输入"></div>
<div class="ledger-field" style="min-width:8rem"><label for="accounts-status">账户状态</label><select id="accounts-status" name="accounts_status"><option value="">全部状态</option>@foreach ($accountStatusOptions as $statusCode => $statusLabel)<option value="{{ $statusCode }}" @selected(request('accounts_status') === $statusCode)>{{ $statusLabel }}</option>@endforeach</select></div>
@ -688,11 +709,16 @@
<div class="ledger-column-actions"><span data-ledger-column-count></span><div class="ledger-column-action-group"><button type="button" data-ledger-column-all>显示全部</button><button type="button" data-ledger-column-reset>恢复默认</button></div></div>
</div>
</div>
<div class="ledger-actions"><button class="ledger-btn primary" type="submit"><span class="material-icons-outlined">search</span>查询</button><a class="ledger-btn" href="{{ $tabUrl('accounts') }}"><span class="material-icons-outlined">refresh</span>重置</a><a class="ledger-btn" href="{{ route('business.financial-ledger.accounts.template') }}" download data-account-template-download><span class="material-icons-outlined">download</span>导出录入模板</a></div>
<div class="ledger-actions"><button class="ledger-btn primary" type="submit"><span class="material-icons-outlined">search</span>查询</button><a class="ledger-btn" href="{{ $accountViewResetUrl }}"><span class="material-icons-outlined">refresh</span>重置</a><a class="ledger-btn" href="{{ route('business.financial-ledger.accounts.template') }}" download data-account-template-download><span class="material-icons-outlined">download</span>导出录入模板</a></div>
<div class="ledger-download-status" data-account-template-download-status role="status" aria-live="polite" hidden>下载已开始,请查看浏览器下载目录。</div>
</form>
<nav class="ledger-subtabbar" aria-label="账户总表账户分类">
<a class="ledger-subtab {{ $accountView === 'all' ? 'is-active' : '' }}" href="{{ $accountViewUrl('all') }}" @if ($accountView === 'all') aria-current="page" @endif>全部账户</a>
<a class="ledger-subtab {{ $accountView === 'bank' ? 'is-active' : '' }}" href="{{ $accountViewUrl('bank') }}" @if ($accountView === 'bank') aria-current="page" @endif>银行账户</a>
<a class="ledger-subtab {{ $accountView === 'non_store' ? 'is-active' : '' }}" href="{{ $accountViewUrl('non_store') }}" @if ($accountView === 'non_store') aria-current="page" @endif>非店铺账户</a>
</nav>
@if ($canEditAccounts)
<dialog class="ledger-company-dialog" data-ledger-company-dialog data-ledger-accounts-url="{{ $tabUrl('accounts') }}" aria-labelledby="ledger-company-dialog-title">
<dialog class="ledger-company-dialog" data-ledger-company-dialog data-ledger-accounts-url="{{ $accountViewResetUrl }}" aria-labelledby="ledger-company-dialog-title">
<form class="ledger-company-form" method="POST" action="{{ route('business.financial-ledger.accounts.store') }}" data-ledger-company-form>
@csrf
<header class="ledger-company-form__header">
@ -944,7 +970,7 @@
</thead>
<tbody>
@forelse ($ledgerAccounts as $row)
<tr>
<tr data-ledger-account-row="{{ $row->account_short }}">
<td>{{ $row->company_name }}</td>
<td>
{{ $row->account_short }}

View File

@ -488,6 +488,62 @@ class FinanceLedgerHeaderControlsTest extends FeatureTestCase
$this->assertStringNotContainsString('data-flow-summary-account="账户甲"', $nonStoreHtml);
}
public function test_account_directory_separates_bank_accounts_and_alipay_only(): void
{
DB::connection('business_mirror')->table('fin_accounts')->insert([
[
'company_name' => '丙公司',
'account_short' => '账户丙支付宝',
'bank_name' => '支付宝',
'account_type' => '公户',
'current_balance' => 0,
'daily_available' => 0,
'blocked_amount' => 0,
],
[
'company_name' => '丁公司',
'account_short' => '账户丁网商',
'bank_name' => '网商银行',
'account_type' => '公户',
'current_balance' => 0,
'daily_available' => 0,
'blocked_amount' => 0,
],
]);
$allHtml = $this->loginAs()->get($this->pageUrl([
'accounts_view' => 'all',
]))->assertOk()->getContent();
$this->assertStringContainsString('>全部账户</a>', $allHtml);
$this->assertStringContainsString('>银行账户</a>', $allHtml);
$this->assertStringContainsString('>非店铺账户</a>', $allHtml);
$this->assertStringContainsString('data-ledger-account-row="账户甲"', $allHtml);
$this->assertStringContainsString('data-ledger-account-row="账户乙"', $allHtml);
$this->assertStringContainsString('data-ledger-account-row="账户丙支付宝"', $allHtml);
$this->assertStringContainsString('data-ledger-account-row="账户丁网商"', $allHtml);
$bankHtml = $this->loginAs()->get($this->pageUrl([
'accounts_view' => 'bank',
]))->assertOk()->getContent();
$this->assertStringContainsString('name="accounts_view" value="bank"', $bankHtml);
$this->assertStringContainsString('data-ledger-account-row="账户甲"', $bankHtml);
$this->assertStringContainsString('data-ledger-account-row="账户丁网商"', $bankHtml);
$this->assertStringNotContainsString('data-ledger-account-row="账户乙"', $bankHtml);
$this->assertStringNotContainsString('data-ledger-account-row="账户丙支付宝"', $bankHtml);
$nonStoreHtml = $this->loginAs()->get($this->pageUrl([
'accounts_view' => 'non_store',
]))->assertOk()->getContent();
$this->assertStringContainsString('name="accounts_view" value="non_store"', $nonStoreHtml);
$this->assertStringContainsString('data-ledger-account-row="账户乙"', $nonStoreHtml);
$this->assertStringContainsString('data-ledger-account-row="账户丙支付宝"', $nonStoreHtml);
$this->assertStringNotContainsString('data-ledger-account-row="账户甲"', $nonStoreHtml);
$this->assertStringNotContainsString('data-ledger-account-row="账户丁网商"', $nonStoreHtml);
}
public function test_account_bank_affiliation_uses_only_the_account_short_suffix(): void
{
$affiliations = [