财务台账:金额按万元展示并增加余额筛选 #440
@ -147,6 +147,15 @@ class FinanceLedgerPageData
|
||||
});
|
||||
}
|
||||
|
||||
$balanceRange = trim((string) request('accounts_balance', ''));
|
||||
if ($balanceRange === '0-5') {
|
||||
$query->where('current_balance', '>=', 0)->where('current_balance', '<', 50000);
|
||||
} elseif ($balanceRange === '5-20') {
|
||||
$query->where('current_balance', '>=', 50000)->where('current_balance', '<', 200000);
|
||||
} elseif ($balanceRange === '20-plus') {
|
||||
$query->where('current_balance', '>=', 200000);
|
||||
}
|
||||
|
||||
$summaryQuery = clone $query;
|
||||
$summary = [
|
||||
'count' => (clone $query)->count(),
|
||||
|
||||
@ -17,8 +17,40 @@
|
||||
request('accounts_status'),
|
||||
request('accounts_region'),
|
||||
request('accounts_type'),
|
||||
request('accounts_balance'),
|
||||
], static fn ($value): bool => trim((string) $value) !== ''));
|
||||
$money = static fn ($value): string => $value === null || $value === '' ? '' : number_format((float) $value, 2);
|
||||
$moneyWan = static function ($value): string {
|
||||
if ($value === null || trim((string) $value) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = trim((string) $value);
|
||||
$parts = preg_split('/\s*\/\s*/', $text);
|
||||
if (! is_array($parts) || count($parts) > 2) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$converted = [];
|
||||
foreach ($parts as $part) {
|
||||
$normalized = str_replace([',', ' '], '', $part);
|
||||
if (! is_numeric($normalized)) {
|
||||
return $text;
|
||||
}
|
||||
$converted[] = number_format((float) $normalized / 10000, 2);
|
||||
}
|
||||
|
||||
return implode(' / ', $converted);
|
||||
};
|
||||
$moneyYuanTitle = static function ($value): string {
|
||||
if ($value === null || trim((string) $value) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$normalized = str_replace([',', ' '], '', trim((string) $value));
|
||||
|
||||
return is_numeric($normalized) ? number_format((float) $normalized, 2) . ' 元' : trim((string) $value);
|
||||
};
|
||||
$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]);
|
||||
@ -208,6 +240,7 @@
|
||||
<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>
|
||||
<div class="ledger-field" style="min-width:8rem"><label for="accounts-region">地区</label><select id="accounts-region" name="accounts_region"><option value="">全部地区</option>@foreach (($accountFilterOptions['regions'] ?? []) as $region)<option value="{{ $region }}" @selected(request('accounts_region') === $region)>{{ $region }}</option>@endforeach<option value="__empty__" @selected(request('accounts_region') === '__empty__')>未填写</option></select></div>
|
||||
<div class="ledger-field" style="min-width:8rem"><label for="accounts-type">账户类型</label><select id="accounts-type" name="accounts_type"><option value="">全部类型</option>@foreach (($accountFilterOptions['types'] ?? []) as $type)<option value="{{ $type }}" @selected(request('accounts_type') === $type)>{{ $type }}</option>@endforeach<option value="__empty__" @selected(request('accounts_type') === '__empty__')>未填写</option></select></div>
|
||||
<div class="ledger-field" style="min-width:8rem"><label for="accounts-balance">当前余额</label><select id="accounts-balance" name="accounts_balance"><option value="">全部金额</option><option value="0-5" @selected(request('accounts_balance') === '0-5')>0-5万</option><option value="5-20" @selected(request('accounts_balance') === '5-20')>5-20万</option><option value="20-plus" @selected(request('accounts_balance') === '20-plus')>20万以上</option></select></div>
|
||||
@if ($activeAccountFilterCount > 0)
|
||||
<span class="ledger-filter-state" data-account-filter-state role="status" aria-live="polite" title="当前有 {{ $activeAccountFilterCount }} 个筛选条件生效"><span class="material-icons-outlined" aria-hidden="true">filter_alt</span><span>筛选中</span><b>{{ $activeAccountFilterCount }}</b></span>
|
||||
@endif
|
||||
@ -229,13 +262,13 @@
|
||||
</form>
|
||||
<div class="ledger-summary">
|
||||
<div class="ledger-summary__item"><span>账户数</span><b>{{ number_format((int) ($ledgerAccountSummary['count'] ?? 0)) }}</b></div>
|
||||
<div class="ledger-summary__item ledger-summary__item--available" data-ledger-summary-available><span>可用金额合计</span><b>{{ $money($ledgerAccountSummary['balance'] ?? 0) }}</b></div>
|
||||
<a class="ledger-summary__item ledger-summary__item--blocked ledger-summary__item--link" data-ledger-summary-blocked href="{{ route('business.financial-ledger.unavailable-balances') }}" aria-label="查看账户异常待办"><span>不可用金额合计</span><b>{{ $money($ledgerAccountSummary['blocked'] ?? 0) }}</b></a>
|
||||
<div class="ledger-summary__item ledger-summary__item--available" data-ledger-summary-available><span>可用金额合计(万元)</span><b title="{{ $moneyYuanTitle($ledgerAccountSummary['balance'] ?? 0) }}">{{ $moneyWan($ledgerAccountSummary['balance'] ?? 0) }}</b></div>
|
||||
<a class="ledger-summary__item ledger-summary__item--blocked ledger-summary__item--link" data-ledger-summary-blocked href="{{ route('business.financial-ledger.unavailable-balances') }}" aria-label="查看账户异常待办"><span>不可用金额合计(万元)</span><b title="{{ $moneyYuanTitle($ledgerAccountSummary['blocked'] ?? 0) }}">{{ $moneyWan($ledgerAccountSummary['blocked'] ?? 0) }}</b></a>
|
||||
<div class="ledger-summary__item"><span>金额口径</span><b>正常计可用,异常计不可用</b></div>
|
||||
</div>
|
||||
<div class="ledger-table-wrap ledger-accounts-table-wrap">
|
||||
<table class="ledger-table ledger-accounts-table">
|
||||
<thead><tr><th>公司名称</th><th>账户简称</th><th>账户类型</th><th class="ledger-status-column">账户状态</th><th>地区</th><th>账号</th><th>开户行</th><th>开户城市/网点</th><th class="ledger-plan-column">公司规划</th><th class="ledger-keep-column">账户是否保留</th><th>后4位</th><th>单笔限额</th><th>日限额</th><th>月限额</th><th>年限额</th><th class="num">当前余额</th><th class="num">可用金额</th><th class="num">不可用金额</th><th class="num">每日可出</th><th class="num">预计出清天数</th><th>提额目标</th><th>提额状态</th><th>提额动作</th><th>已做动作</th><th>下一步</th><th>备注</th><th>责任人</th></tr></thead>
|
||||
<thead><tr><th>公司名称</th><th>账户简称</th><th>账户类型</th><th class="ledger-status-column">账户状态</th><th>地区</th><th>账号</th><th>开户行</th><th>开户城市/网点</th><th class="ledger-plan-column">公司规划</th><th class="ledger-keep-column">账户是否保留</th><th>后4位</th><th>单笔限额(万元)</th><th>日限额(万元)</th><th>月限额(万元)</th><th>年限额(万元)</th><th class="num">当前余额(万元)</th><th class="num">可用金额(万元)</th><th class="num">不可用金额(万元)</th><th class="num">每日可出(万元)</th><th class="num">预计出清天数</th><th>提额目标(万元)</th><th>提额状态</th><th>提额动作</th><th>已做动作</th><th>下一步</th><th>备注</th><th>责任人</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse ($ledgerAccounts as $row)
|
||||
<tr>
|
||||
@ -289,7 +322,7 @@
|
||||
</select>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $row->account_last4 }}</td><td>{{ $row->single_limit }}</td><td>{{ $row->daily_limit }}</td><td>{{ $row->monthly_limit }}</td><td>{{ $row->yearly_limit }}</td><td class="num" data-account-current-balance="{{ (float) $row->current_balance }}">{{ $money($row->current_balance) }}</td><td class="num" data-account-available-amount>{{ $money($row->daily_available) }}</td><td class="num" data-account-blocked-amount>{{ $money($row->blocked_amount) }}</td><td class="num" data-account-daily-available-amount>{{ $money($row->daily_available) }}</td><td class="num">{{ $row->clearing_days !== null ? number_format((float) $row->clearing_days, 2) : '' }}</td><td>{{ $row->raise_target }}</td><td>{{ $row->raise_status }}</td><td>{{ $row->raise_actions }}</td><td>{{ $row->done_actions }}</td><td>{{ $row->next_action }}</td><td>{{ $row->remark }}</td><td>{{ $row->owner }}</td>
|
||||
<td>{{ $row->account_last4 }}</td><td title="{{ $moneyYuanTitle($row->single_limit) }}">{{ $moneyWan($row->single_limit) }}</td><td title="{{ $moneyYuanTitle($row->daily_limit) }}">{{ $moneyWan($row->daily_limit) }}</td><td title="{{ $moneyYuanTitle($row->monthly_limit) }}">{{ $moneyWan($row->monthly_limit) }}</td><td title="{{ $moneyYuanTitle($row->yearly_limit) }}">{{ $moneyWan($row->yearly_limit) }}</td><td class="num" data-account-current-balance="{{ (float) $row->current_balance }}" title="{{ $moneyYuanTitle($row->current_balance) }}">{{ $moneyWan($row->current_balance) }}</td><td class="num" data-account-available-amount title="{{ $moneyYuanTitle($row->daily_available) }}">{{ $moneyWan($row->daily_available) }}</td><td class="num" data-account-blocked-amount title="{{ $moneyYuanTitle($row->blocked_amount) }}">{{ $moneyWan($row->blocked_amount) }}</td><td class="num" data-account-daily-available-amount title="{{ $moneyYuanTitle($row->daily_available) }}">{{ $moneyWan($row->daily_available) }}</td><td class="num">{{ $row->clearing_days !== null ? number_format((float) $row->clearing_days, 2) : '' }}</td><td title="{{ $moneyYuanTitle($row->raise_target) }}">{{ $moneyWan($row->raise_target) }}</td><td>{{ $row->raise_status }}</td><td>{{ $row->raise_actions }}</td><td>{{ $row->done_actions }}</td><td>{{ $row->next_action }}</td><td>{{ $row->remark }}</td><td>{{ $row->owner }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="27"><div class="ledger-empty">没有匹配的账户数据。</div></td></tr>
|
||||
@ -972,6 +1005,11 @@
|
||||
const currentCell = row?.querySelector('[data-account-current-balance]');
|
||||
const currentAmount = Number(currentCell?.dataset.accountCurrentBalance || 0);
|
||||
const currentText = currentCell?.textContent?.trim() || '0.00';
|
||||
const currentAmountWan = currentAmount / 10000;
|
||||
const currentYuanTitle = currentCell?.title || currentAmount.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
}) + ' 元';
|
||||
const nextNormal = value === 'normal';
|
||||
const previousNormal = previous === 'normal';
|
||||
const availableCell = row?.querySelector('[data-account-available-amount]');
|
||||
@ -980,24 +1018,31 @@
|
||||
if (availableCell) availableCell.textContent = nextNormal ? currentText : '0.00';
|
||||
if (blockedCell) blockedCell.textContent = nextNormal ? '0.00' : currentText;
|
||||
if (dailyAvailableCell) dailyAvailableCell.textContent = nextNormal ? currentText : '0.00';
|
||||
if (availableCell) availableCell.title = nextNormal ? currentYuanTitle : '0.00 元';
|
||||
if (blockedCell) blockedCell.title = nextNormal ? '0.00 元' : currentYuanTitle;
|
||||
if (dailyAvailableCell) dailyAvailableCell.title = nextNormal ? currentYuanTitle : '0.00 元';
|
||||
|
||||
if (nextNormal !== previousNormal) {
|
||||
const formatSummaryAmount = (amount) => amount.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
const adjustSummary = (card, delta) => {
|
||||
const adjustSummary = (card, displayDelta, yuanDelta) => {
|
||||
const valueNode = card?.querySelector('b');
|
||||
if (!valueNode) return;
|
||||
const current = Number(valueNode.textContent.replace(/,/g, '')) || 0;
|
||||
valueNode.textContent = formatSummaryAmount(Math.max(0, current + delta));
|
||||
valueNode.textContent = formatSummaryAmount(Math.max(0, current + displayDelta));
|
||||
const currentYuan = Number((valueNode.title || '').replace(/[^\d.-]/g, '')) || 0;
|
||||
valueNode.title = formatSummaryAmount(Math.max(0, currentYuan + yuanDelta)) + ' 元';
|
||||
};
|
||||
adjustSummary(
|
||||
page.querySelector('[data-ledger-summary-available]'),
|
||||
nextNormal ? currentAmountWan : -currentAmountWan,
|
||||
nextNormal ? currentAmount : -currentAmount,
|
||||
);
|
||||
adjustSummary(
|
||||
page.querySelector('[data-ledger-summary-blocked]'),
|
||||
nextNormal ? -currentAmountWan : currentAmountWan,
|
||||
nextNormal ? -currentAmount : currentAmount,
|
||||
);
|
||||
}
|
||||
|
||||
@ -94,6 +94,10 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase
|
||||
->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)
|
||||
@ -124,10 +128,11 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase
|
||||
->assertSee('恢复默认')
|
||||
->assertSee('可用金额合计')
|
||||
->assertSee('不可用金额合计')
|
||||
->assertSee('可用金额合计(万元)')
|
||||
->assertSee('href="' . $this->unavailableBalancesUrl() . '"', false)
|
||||
->assertSee('查看账户异常待办')
|
||||
->assertSee('正常计可用,异常计不可用')
|
||||
->assertSee('<th class="num">当前余额</th><th class="num">可用金额</th><th class="num">不可用金额</th>', false)
|
||||
->assertSee('<th class="num">当前余额(万元)</th><th class="num">可用金额(万元)</th><th class="num">不可用金额(万元)</th>', false)
|
||||
->assertSee('data-ledger-summary-available', false)
|
||||
->assertSee('data-account-current-balance="321.45"', false)
|
||||
->assertSee('data-account-available-amount', false)
|
||||
@ -137,6 +142,80 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase
|
||||
->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);
|
||||
}
|
||||
|
||||
public function test_unavailable_balance_tasks_page_is_separate_and_only_lists_blocked_accounts(): void
|
||||
{
|
||||
$connection = DB::connection('business_mirror');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user