feat: 店铺维度报表无业绩月份列默认隐藏

- 所有店铺当月都没有业绩的销量月份列默认隐藏,仍保留在「筛选字段」中可手动勾选显示
- 无数据月份列计算统一为 columnsWithoutData:收入以 null/空表示无账单、0 算有账单,销量 0 视为无业绩
- 补充单元测试与页面测试;页面测试断言改为只匹配表头标签,避免命中内联样式中的同名字符串
This commit is contained in:
WuYongJie 2026-10-07 10:00:55 +08:00
parent 5ef4119a3d
commit b8175cfb00
4 changed files with 117 additions and 14 deletions

View File

@ -152,6 +152,13 @@ SQL);
{
$filters = StoreReportRepository::requestFilters();
$rows = $this->storeReportRows($filters);
// 所有店铺该月都没有业绩的月份列默认隐藏;「筛选字段」中仍保留勾选项,可手动选出来。
request()->attributes->set(
'store-report.default_hidden_columns',
$this->columnsWithoutData($rows, 'sales_', zeroCountsAsData: false),
);
$offset = ($page - 1) * $perPage;
return ['data' => array_slice($rows, $offset, $perPage), 'total' => count($rows)];
@ -299,7 +306,7 @@ SQL);
// 所有主体该月收入均为空时,该月份列在页面上默认隐藏;「筛选字段」中仍保留勾选项,可手动选出来。
request()->attributes->set(
'subject-operating-report.default_hidden_columns',
$this->subjectReportColumnsWithoutData($rows),
$this->columnsWithoutData($rows, 'income_', zeroCountsAsData: true),
);
$rows = array_values(array_filter($rows, fn (array $row): bool => $this->subjectMatchesFilters($row, $filters)));
@ -311,23 +318,28 @@ SQL);
}
/**
* 没有数据的月份列:全部主体该月收入都为空(null/空字符串)时才视为没有数据。
* 金额为 0 的月份代表当月有账单,不隐藏。
* 没有数据的月份列:所有行该月都没有数值时返回该列名,页面据此默认隐藏。
*
* 收入(income_)以 null/空字符串表示无账单,金额 0 也算有账单;
* 销量(sales_)没有空值标记,数值 0 视为当月没有业绩。
*
* @param array<int,array<string,mixed>> $rows
* @return array<int,string>
*/
private function subjectReportColumnsWithoutData(array $rows): array
private function columnsWithoutData(array $rows, string $prefix, bool $zeroCountsAsData): array
{
$columns = [];
for ($month = 1; $month <= 12; $month++) {
$key = sprintf('income_%02d', $month);
$key = sprintf('%s%02d', $prefix, $month);
foreach ($rows as $row) {
$value = $row[$key] ?? null;
if ($value === null || $value === '') {
continue;
}
if (! $zeroCountsAsData && (float) $value === 0.0) {
continue;
}
continue 2;
}

View File

@ -1125,12 +1125,17 @@
array_filter(explode(',', (string) request('columns', ''))),
$directoryColumnNames,
));
// 主体维度经营报表:没有数据的月份列默认隐藏,仍保留在「筛选字段」中供手动勾选显示。
// 主体维度经营报表、店铺维度报表:没有数据的月份列默认隐藏,仍保留在「筛选字段」中供手动勾选显示。
$directoryDefaultColumns = $directoryColumnNames;
if ($directoryPageKey === 'company-management/subject-operating-report') {
$directoryDefaultHiddenColumns = match ($directoryPageKey) {
'company-management/subject-operating-report' => request()->attributes->get('subject-operating-report.default_hidden_columns', []),
'store-management/store-report' => request()->attributes->get('store-report.default_hidden_columns', []),
default => [],
};
if ((array) $directoryDefaultHiddenColumns !== []) {
$directoryDefaultColumns = array_values(array_diff(
$directoryColumnNames,
(array) request()->attributes->get('subject-operating-report.default_hidden_columns', []),
(array) $directoryDefaultHiddenColumns,
));
}
$visibleDirectoryColumns = request()->has('columns') ? $requestedDirectoryColumns : $directoryDefaultColumns;

View File

@ -177,9 +177,9 @@ class PrototypePageTest extends FeatureTestCase
$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->assertMatchesRegularExpression('/<th[^>]*data-column-key="income_01"[^>]*is-column-hidden/', $html);
$this->assertMatchesRegularExpression('/<th[^>]*data-column-key="income_02"[^>]*is-column-hidden/', $html);
$this->assertDoesNotMatchRegularExpression('/<th[^>]*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);
@ -194,8 +194,64 @@ class PrototypePageTest extends FeatureTestCase
$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);
$this->assertDoesNotMatchRegularExpression('/<th[^>]*data-column-key="income_01"[^>]*is-column-hidden/', $restoredHtml);
$this->assertDoesNotMatchRegularExpression('/<th[^>]*data-column-key="income_03"[^>]*is-column-hidden/', $restoredHtml);
$this->assertMatchesRegularExpression('/<th[^>]*data-column-key="income_04"[^>]*is-column-hidden/', $restoredHtml);
}
public function testStoreReportHidesSalesMonthsWithoutDataAndKeepsThemSelectable(): void
{
$service = Mockery::mock(UpstreamBusinessInformation::class);
$service->shouldReceive('paginate')->with('store-management', 'store-report', 50)
->andReturnUsing(function (): LengthAwarePaginator {
request()->attributes->set(
'store-report.default_hidden_columns',
['sales_01', 'sales_08'],
);
return new LengthAwarePaginator([(object) [
'erp_store_name' => 'ERP测试店铺',
'store_name' => '前台测试店铺',
'store_id' => 'SHOP-001',
'status' => '开店中',
'entity' => '测试主体',
'company_type' => '普通公司',
'period_sales' => 100,
'sales_01' => 0,
'sales_07' => 100,
'sales_08' => 0,
]], 1, 50, 1, ['path' => '/']);
});
$this->app->instance(UpstreamBusinessInformation::class, $service);
$response = $this->loginAs()->get(route('business.pages.show', [
'section' => 'store-management',
'page' => 'store-report',
]));
$response->assertOk();
$html = $response->getContent();
// 没有业绩的月份默认隐藏,但表头和「筛选字段」勾选项仍在页面中。
$this->assertMatchesRegularExpression('/<th[^>]*data-column-key="sales_01"[^>]*is-column-hidden/', $html);
$this->assertMatchesRegularExpression('/<th[^>]*data-column-key="sales_08"[^>]*is-column-hidden/', $html);
$this->assertDoesNotMatchRegularExpression('/<th[^>]*data-column-key="sales_07"[^>]*is-column-hidden/', $html);
$this->assertStringContainsString('value="sales_01" data-directory-query-column', $html);
$this->assertStringNotContainsString('value="sales_01" data-directory-query-column checked', $html);
$this->assertStringContainsString('value="sales_07" data-directory-query-column checked', $html);
$this->assertStringContainsString('筛选字段 24/26', $html);
// 手动勾选(columns 参数)后,对应月份列恢复显示。
$restored = $this->get(route('business.pages.show', [
'section' => 'store-management',
'page' => 'store-report',
'columns' => 'erp_store_name,store_name,store_id,status,entity,period_sales,sales_01,sales_07',
]));
$restored->assertOk();
$restoredHtml = $restored->getContent();
$this->assertDoesNotMatchRegularExpression('/<th[^>]*data-column-key="sales_01"[^>]*is-column-hidden/', $restoredHtml);
$this->assertDoesNotMatchRegularExpression('/<th[^>]*data-column-key="sales_07"[^>]*is-column-hidden/', $restoredHtml);
$this->assertMatchesRegularExpression('/<th[^>]*data-column-key="sales_08"[^>]*is-column-hidden/', $restoredHtml);
}
}

View File

@ -238,6 +238,36 @@ class StoreReportRepositoryTest extends TestCase
$this->assertSame(60.0, $paginator->items()[0]->period_sales);
}
public function test_store_report_hides_sales_months_without_data_by_default(): void
{
Carbon::setTestNow('2026-09-15 10:00:00');
$this->insertBill('店铺A', '2026-07-05', 60);
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->with('store-information.read')->andReturnTrue();
$mirror->shouldReceive('allStores')->once()->andReturn([[
'erp_store_name' => '店铺A', 'company_name' => '主体甲', 'store_status' => '0',
]]);
$accounts = Mockery::mock(StoreAccountMirrorRepository::class);
$accounts->shouldReceive('ownershipByStoreName')->andReturn([]);
$service = new UpstreamBusinessInformation(
Mockery::mock(UpstreamQueryCacheFactory::class),
$mirror,
Mockery::mock(InstallationDetailMirrorRepository::class),
$this->repository,
$accounts,
);
$paginator = $service->paginate('store-management', 'store-report', 50);
// 只有 7 月有业绩,其余月份列默认隐藏。
$this->assertSame(
['sales_01', 'sales_02', 'sales_03', 'sales_04', 'sales_05', 'sales_06', 'sales_08', 'sales_09', 'sales_10', 'sales_11', 'sales_12'],
$this->app['request']->attributes->get('store-report.default_hidden_columns'),
);
$this->assertSame(1, $paginator->total());
$this->assertSame(60.0, (float) $paginator->items()[0]->sales_07);
}
public function test_subject_operating_report_aggregates_income_by_entity(): void
{
Carbon::setTestNow('2026-09-01 10:00:00');