feat: 主体维度经营报表无数据月份列默认隐藏 (#280)

This commit is contained in:
DoDo 2026-10-06 23:51:16 +08:00
parent 5d7bc3f2c4
commit 5ef4119a3d
5 changed files with 153 additions and 3 deletions

View File

@ -296,6 +296,12 @@ SQL);
}
unset($row);
// 所有主体该月收入均为空时,该月份列在页面上默认隐藏;「筛选字段」中仍保留勾选项,可手动选出来。
request()->attributes->set(
'subject-operating-report.default_hidden_columns',
$this->subjectReportColumnsWithoutData($rows),
);
$rows = array_values(array_filter($rows, fn (array $row): bool => $this->subjectMatchesFilters($row, $filters)));
$this->sortSubjectRows($rows, $filters);
$offset = ($page - 1) * $perPage;
@ -304,6 +310,34 @@ SQL);
return ['data' => $pageRows, 'total' => count($rows)];
}
/**
* 没有数据的月份列:全部主体该月收入都为空(null/空字符串)时才视为没有数据。
* 金额为 0 的月份代表当月有账单,不隐藏。
*
* @param array<int,array<string,mixed>> $rows
* @return array<int,string>
*/
private function subjectReportColumnsWithoutData(array $rows): array
{
$columns = [];
for ($month = 1; $month <= 12; $month++) {
$key = sprintf('income_%02d', $month);
foreach ($rows as $row) {
$value = $row[$key] ?? null;
if ($value === null || $value === '') {
continue;
}
continue 2;
}
$columns[] = $key;
}
return $columns;
}
/** @param array<string,mixed> $row @param array<string,string> $filters */
private function subjectMatchesFilters(array $row, array $filters): bool
{

View File

@ -1125,7 +1125,15 @@
array_filter(explode(',', (string) request('columns', ''))),
$directoryColumnNames,
));
$visibleDirectoryColumns = request()->has('columns') ? $requestedDirectoryColumns : $directoryColumnNames;
// 主体维度经营报表:没有数据的月份列默认隐藏,仍保留在「筛选字段」中供手动勾选显示。
$directoryDefaultColumns = $directoryColumnNames;
if ($directoryPageKey === 'company-management/subject-operating-report') {
$directoryDefaultColumns = array_values(array_diff(
$directoryColumnNames,
(array) request()->attributes->get('subject-operating-report.default_hidden_columns', []),
));
}
$visibleDirectoryColumns = request()->has('columns') ? $requestedDirectoryColumns : $directoryDefaultColumns;
$directoryQueryFormId = 'directory-query-form-' . ($page['slug'] ?? 'page');
@endphp
@if ($directoryUsesFieldQuery)
@ -1251,7 +1259,7 @@
@endif
<div class="directory-query-actions">
<details class="directory-query-picker" data-directory-query-picker>
<summary>筛选字段</summary>
<summary>筛选字段{{ count($visibleDirectoryColumns) < count($directoryFields) ? ' ' . count($visibleDirectoryColumns) . '/' . count($directoryFields) : '' }}</summary>
<div class="directory-query-picker__panel" role="group" aria-label="勾选展示字段">
<span class="directory-query-picker__hint">勾选需要显示的表头,可重复调整。</span>
@foreach ($directoryFields as $field)

View File

@ -530,8 +530,10 @@
label.draggable = true;
const key = header.dataset.financeColumnKey;
const fixed = Boolean(header.dataset.financeFixed);
// 服务端按业务规则默认隐藏的列(如无数据的月份)保持取消勾选,可在这里手动选出来。
const visible = fixed || !header.classList.contains('is-column-hidden');
label.dataset.financeOptionKey = key;
label.innerHTML = `<input type="checkbox" value="${key.replaceAll('"', '&quot;')}" data-finance-field-check ${fixed ? 'checked disabled' : 'checked'}><span>${header.textContent.trim() || '未命名字段'}</span>`;
label.innerHTML = `<input type="checkbox" value="${key.replaceAll('"', '&quot;')}" data-finance-field-check ${fixed ? 'checked disabled' : (visible ? 'checked' : '')}><span>${header.textContent.trim() || '未命名字段'}</span>`;
panel.appendChild(label);
});
const actions = document.createElement('div');

View File

@ -142,4 +142,60 @@ class PrototypePageTest extends FeatureTestCase
->assertOk()
->assertSeeTextInOrder(['最近12个月累计收入', '1月收入', '12月收入']);
}
public function testSubjectOperatingReportHidesMonthsWithoutDataAndKeepsThemSelectable(): void
{
$service = Mockery::mock(UpstreamBusinessInformation::class);
$service->shouldReceive('paginate')->with('company-management', 'subject-operating-report', 50)
->andReturnUsing(function (): LengthAwarePaginator {
request()->attributes->set(
'subject-operating-report.default_hidden_columns',
['income_01', 'income_02'],
);
return new LengthAwarePaginator([(object) [
'entity' => '测试主体',
'city' => '常州市',
'company_type' => '有限公司',
'rolling_12m_income' => 100,
'income_01' => null,
'income_02' => null,
'income_03' => 50,
'can_change_entity' => null,
'plan' => null,
'new_entity' => null,
]], 1, 50, 1, ['path' => '/']);
});
$this->app->instance(UpstreamBusinessInformation::class, $service);
$response = $this->loginAs()->get(route('business.pages.show', [
'section' => 'company-management',
'page' => 'subject-operating-report',
]));
$response->assertOk();
$html = $response->getContent();
// 没有数据的月份默认隐藏,但表头和「筛选字段」勾选项仍在页面中。
$this->assertMatchesRegularExpression('/data-column-key="income_01"[^>]*is-column-hidden/', $html);
$this->assertMatchesRegularExpression('/data-column-key="income_02"[^>]*is-column-hidden/', $html);
$this->assertDoesNotMatchRegularExpression('/data-column-key="income_03"[^>]*is-column-hidden/', $html);
$this->assertStringContainsString('value="income_01" data-directory-query-column', $html);
$this->assertStringNotContainsString('value="income_01" data-directory-query-column checked', $html);
$this->assertStringContainsString('value="income_03" data-directory-query-column checked', $html);
$this->assertStringContainsString('筛选字段 17/19', $html);
// 手动勾选(columns 参数)后,对应月份列恢复显示。
$restored = $this->get(route('business.pages.show', [
'section' => 'company-management',
'page' => 'subject-operating-report',
'columns' => 'city,entity,company_type,rolling_12m_income,income_01,income_03',
]));
$restored->assertOk();
$restoredHtml = $restored->getContent();
$this->assertDoesNotMatchRegularExpression('/data-column-key="income_01"[^>]*is-column-hidden/', $restoredHtml);
$this->assertDoesNotMatchRegularExpression('/data-column-key="income_03"[^>]*is-column-hidden/', $restoredHtml);
$this->assertMatchesRegularExpression('/data-column-key="income_04"[^>]*is-column-hidden/', $restoredHtml);
}
}

View File

@ -7,6 +7,8 @@ use Closure;
use Mockery;
use Modules\EcommerceFinance\Repositories\BusinessInformationMirrorRepository;
use Modules\EcommerceFinance\Repositories\InstallationDetailMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreAccountMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreReportRepository;
use Modules\EcommerceFinance\Services\UpstreamQueryCache;
use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory;
use Tests\TestCase;
@ -123,6 +125,54 @@ class UpstreamBusinessInformationTest extends TestCase
->paginate('store-management', 'basic-information');
}
public function test_subject_operating_report_hides_income_months_without_data_by_default(): void
{
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->with('store-information.read')->andReturnTrue();
$mirror->shouldReceive('allStores')->andReturn([['erp_store_name' => '测试店铺']]);
$mirror->shouldReceive('ready')->with('company-information.read')->andReturnTrue();
$mirror->shouldReceive('companiesByNames')->andReturn([]);
$storeReports = Mockery::mock(StoreReportRepository::class);
$storeReports->shouldReceive('rows')->andReturn([[
'entity' => '测试主体',
'company_type' => '店铺-普通公司',
'rolling_12m_income' => 188.5,
'income_01' => null,
'income_02' => 88.5,
'income_03' => 0.0,
'income_04' => null,
'income_05' => null,
'income_06' => null,
'income_07' => null,
'income_08' => null,
'income_09' => null,
'income_10' => null,
'income_11' => null,
'income_12' => null,
]]);
$storeAccounts = Mockery::mock(StoreAccountMirrorRepository::class);
$storeAccounts->shouldReceive('ownershipByStoreName')->andReturn([]);
$information = new UpstreamBusinessInformation(
Mockery::mock(UpstreamQueryCacheFactory::class),
$mirror,
Mockery::mock(InstallationDetailMirrorRepository::class),
$storeReports,
$storeAccounts,
);
$paginator = $information->paginate('company-management', 'subject-operating-report', 50);
// income_02 有金额、income_03 为 0 都算有数据,其余空月份默认隐藏。
$this->assertSame(
['income_01', 'income_04', 'income_05', 'income_06', 'income_07', 'income_08', 'income_09', 'income_10', 'income_11', 'income_12'],
$this->app['request']->attributes->get('subject-operating-report.default_hidden_columns'),
);
$this->assertSame(1, $paginator->total());
$this->assertSame('测试主体', $paginator->items()[0]->entity);
}
public static function businessInformationPages(): array
{
return [