合并财务报表表格查询布局并修正筛选排序 #274
@ -197,16 +197,20 @@ SQL);
|
||||
*/
|
||||
private function storeMatchesFilters(array $row, array $filters): bool
|
||||
{
|
||||
foreach (['erp_store_name', 'store_name', 'store_id', 'profit_center', 'store_manager', 'department_manager', 'company_type'] as $key) {
|
||||
foreach (['erp_store_name', 'store_name', 'store_id', 'profit_center', 'store_manager', 'department_manager'] as $key) {
|
||||
$expected = trim((string) ($filters[$key] ?? ''));
|
||||
if ($expected === '') {
|
||||
continue;
|
||||
}
|
||||
$actual = trim((string) ($row[$key] ?? ''));
|
||||
if ($actual !== $expected) {
|
||||
if (! str_contains(mb_strtolower($actual), mb_strtolower($expected))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$companyType = trim((string) ($filters['company_type'] ?? ''));
|
||||
if ($companyType !== '' && trim((string) ($row['company_type'] ?? '')) !== $companyType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$statusMap = ['0' => '正常', '3' => '关闭中', '6' => '已关闭'];
|
||||
$status = trim((string) ($filters['status'] ?? ''));
|
||||
@ -252,7 +256,8 @@ SQL);
|
||||
|
||||
private function subjectOperatingReportPage(int $page, int $perPage): array
|
||||
{
|
||||
$rows = $this->storeReportRows();
|
||||
$filters = $this->pageFilters('company-management', 'subject-operating-report');
|
||||
$rows = $this->storeReportRows(array_intersect_key($filters, array_flip(['company_type'])));
|
||||
$entities = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
@ -284,20 +289,74 @@ SQL);
|
||||
}
|
||||
|
||||
$rows = array_values($entities);
|
||||
usort($rows, static fn (array $left, array $right): int => strcmp($left['entity'], $right['entity']));
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$pageRows = array_slice($rows, $offset, $perPage);
|
||||
$details = $this->companyDetails(array_column($pageRows, 'entity'));
|
||||
foreach ($pageRows as &$row) {
|
||||
$details = $this->companyDetails(array_column($rows, 'entity'));
|
||||
foreach ($rows as &$row) {
|
||||
$row['company_details'] = $details[$row['entity']] ?? [];
|
||||
$row['city'] = $this->cityFromDetails($row['company_details']);
|
||||
}
|
||||
unset($row);
|
||||
|
||||
$rows = array_values(array_filter($rows, fn (array $row): bool => $this->subjectMatchesFilters($row, $filters)));
|
||||
$this->sortSubjectRows($rows, $filters);
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$pageRows = array_slice($rows, $offset, $perPage);
|
||||
|
||||
return ['data' => $pageRows, 'total' => count($rows)];
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $row @param array<string,string> $filters */
|
||||
private function subjectMatchesFilters(array $row, array $filters): bool
|
||||
{
|
||||
if (($filters['company_type'] ?? '') !== '' && (string) ($row['company_type'] ?? '') !== (string) $filters['company_type']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (['city', 'entity', 'plan', 'new_entity'] as $key) {
|
||||
$needle = trim((string) ($filters[$key] ?? ''));
|
||||
if ($needle !== '' && mb_stripos((string) ($row[$key] ?? ''), $needle) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['rolling_12m_income', ...array_map(static fn (int $month): string => sprintf('income_%02d', $month), range(1, 12))] as $key) {
|
||||
$needle = str_replace(',', '', trim((string) ($filters[$key] ?? '')));
|
||||
if ($needle !== '' && (! is_numeric($needle) || ! is_numeric($row[$key] ?? null)
|
||||
|| abs((float) $row[$key] - (float) $needle) > 0.000001)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$canChange = trim((string) ($filters['can_change_entity'] ?? ''));
|
||||
return $canChange === '' || (string) ($row['can_change_entity'] ?? '') === $canChange;
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $rows @param array<string,string> $filters */
|
||||
private function sortSubjectRows(array &$rows, array $filters): void
|
||||
{
|
||||
$key = (string) ($filters['_sort'] ?? 'entity');
|
||||
$allowed = [
|
||||
'city', 'entity', 'company_type', 'rolling_12m_income',
|
||||
...array_map(static fn (int $month): string => sprintf('income_%02d', $month), range(1, 12)),
|
||||
'can_change_entity', 'plan', 'new_entity',
|
||||
];
|
||||
if (! in_array($key, $allowed, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$direction = ($filters['_direction'] ?? 'asc') === 'desc' ? -1 : 1;
|
||||
usort($rows, static function (array $left, array $right) use ($key, $direction): int {
|
||||
$leftValue = $left[$key] ?? null;
|
||||
$rightValue = $right[$key] ?? null;
|
||||
if (is_numeric($leftValue) && is_numeric($rightValue)) {
|
||||
$comparison = ((float) $leftValue <=> (float) $rightValue) * $direction;
|
||||
} else {
|
||||
$comparison = strcmp((string) ($leftValue ?? ''), (string) ($rightValue ?? '')) * $direction;
|
||||
}
|
||||
|
||||
return $comparison !== 0 ? $comparison : strcmp((string) ($left['entity'] ?? ''), (string) ($right['entity'] ?? ''));
|
||||
});
|
||||
}
|
||||
|
||||
private function sumNullable(array &$row, string $key, mixed $value): void
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
|
||||
@ -118,7 +118,7 @@ SQL)
|
||||
] as $key => $column) {
|
||||
$value = trim((string) ($filters[$key] ?? ''));
|
||||
if ($value !== '') {
|
||||
$query->where($column, '=', $value);
|
||||
$query->where($column, 'like', '%' . addcslashes($value, '%_\\') . '%');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -52,10 +52,27 @@ class StoreReportRepository
|
||||
foreach (['erp_store_name', 'store_name', 'store_id', 'profit_center', 'store_manager', 'department_manager', 'entity', 'company_type'] as $key) {
|
||||
$rules[$key] = ['nullable', 'string', 'max:191'];
|
||||
}
|
||||
foreach (['period_sales', ...array_map(static fn (int $month): string => sprintf('sales_%02d', $month), range(1, 12))] as $key) {
|
||||
$rules[$key] = ['nullable', 'string', 'max:64'];
|
||||
}
|
||||
$rules['status'] = ['nullable', 'string', 'in:0,3,6'];
|
||||
$rules['can_change'] = ['nullable', 'string', 'in:是,否'];
|
||||
$sortFields = [
|
||||
'erp_store_name', 'store_name', 'store_id', 'profit_center', 'store_manager', 'department_manager',
|
||||
'opened_at', 'status', 'can_change', 'entity', 'company_type', 'effective_at',
|
||||
'invalid_at', 'period_sales', ...array_map(static fn (int $month): string => sprintf('sales_%02d', $month), range(1, 12)),
|
||||
];
|
||||
$rules['sort'] = ['nullable', 'string', 'in:' . implode(',', $sortFields)];
|
||||
$rules['direction'] = ['nullable', 'string', 'in:asc,desc'];
|
||||
|
||||
return validator(request()->only(array_keys($rules)), $rules)->validate();
|
||||
$filters = validator(request()->only(array_keys($rules)), $rules)->validate();
|
||||
if (! empty($filters['sort'])) {
|
||||
$filters['_sort'] = $filters['sort'];
|
||||
$filters['_direction'] = ($filters['direction'] ?? 'asc') === 'desc' ? 'desc' : 'asc';
|
||||
}
|
||||
unset($filters['sort'], $filters['direction']);
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,6 +163,47 @@ class StoreReportRepository
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sortRows($rows, $filters);
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $rows @param array<string,string> $filters */
|
||||
private function sortRows(array $rows, array $filters): array
|
||||
{
|
||||
$key = (string) ($filters['_sort'] ?? '');
|
||||
if ($key === '') {
|
||||
return $rows;
|
||||
}
|
||||
|
||||
$allowed = [
|
||||
'erp_store_name' => 'erp_store_name', 'store_name' => 'store_name', 'store_id' => 'store_id',
|
||||
'profit_center' => 'profit_center', 'store_manager' => 'store_manager',
|
||||
'department_manager' => 'department_manager', 'opened_at' => 'opened_at',
|
||||
'status' => 'status', 'can_change' => 'can_change', 'entity' => 'entity',
|
||||
'company_type' => 'company_type', 'effective_at' => 'effective_at',
|
||||
'invalid_at' => 'invalid_at', 'period_sales' => 'period_sales',
|
||||
...array_combine(
|
||||
array_map(static fn (int $month): string => sprintf('sales_%02d', $month), range(1, 12)),
|
||||
array_map(static fn (int $month): string => sprintf('sales_%02d', $month), range(1, 12)),
|
||||
),
|
||||
];
|
||||
$column = $allowed[$key] ?? null;
|
||||
if ($column === null) {
|
||||
return $rows;
|
||||
}
|
||||
|
||||
$direction = ($filters['_direction'] ?? 'asc') === 'desc' ? -1 : 1;
|
||||
usort($rows, static function (array $left, array $right) use ($column, $direction): int {
|
||||
$leftValue = $left[$column] ?? null;
|
||||
$rightValue = $right[$column] ?? null;
|
||||
if (is_numeric($leftValue) && is_numeric($rightValue)) {
|
||||
$comparison = ((float) $leftValue <=> (float) $rightValue) * $direction;
|
||||
} else {
|
||||
$comparison = strcmp((string) ($leftValue ?? ''), (string) ($rightValue ?? '')) * $direction;
|
||||
}
|
||||
|
||||
return $comparison !== 0 ? $comparison : strcmp((string) ($left['store_id'] ?? ''), (string) ($right['store_id'] ?? ''));
|
||||
});
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
@ -302,7 +360,7 @@ class StoreReportRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* 生命周期字段的行级过滤:主体、能否变更、负责人、利润中心精确匹配;生效、失效时间左闭右开。
|
||||
* 生命周期字段的行级过滤:主体、负责人和利润中心按关键词匹配,能否变更按原值匹配;生效、失效时间左闭右开。
|
||||
*
|
||||
* 店铺负责人/部门负责人/利润中心展示店铺账号镜像(PostgreSQL)覆盖后的值,
|
||||
* 与页面筛选保持一致,因此在这里按行值匹配而不是上游镜像 SQL。
|
||||
@ -312,12 +370,16 @@ class StoreReportRepository
|
||||
*/
|
||||
private function matchesFilters(array $row, string $expiresAt, array $filters): bool
|
||||
{
|
||||
foreach (['entity', 'can_change', 'store_manager', 'department_manager', 'profit_center'] as $key) {
|
||||
foreach (['entity', 'store_manager', 'department_manager', 'profit_center'] as $key) {
|
||||
$expected = trim((string) ($filters[$key] ?? ''));
|
||||
if ($expected !== '' && trim((string) ($row[$key] ?? '')) !== $expected) {
|
||||
if ($expected !== '' && ! str_contains(mb_strtolower(trim((string) ($row[$key] ?? ''))), mb_strtolower($expected))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$canChange = trim((string) ($filters['can_change'] ?? ''));
|
||||
if ($canChange !== '' && trim((string) ($row['can_change'] ?? '')) !== $canChange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (['effective_at' => (string) $row['effective_at'], 'invalid_at' => $expiresAt] as $key => $value) {
|
||||
$from = trim((string) ($filters[$key . '_from'] ?? ''));
|
||||
@ -330,6 +392,14 @@ class StoreReportRepository
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['period_sales', ...array_map(static fn (int $month): string => sprintf('sales_%02d', $month), range(1, 12))] as $key) {
|
||||
$needle = str_replace(',', '', trim((string) ($filters[$key] ?? '')));
|
||||
if ($needle !== '' && (! is_numeric($needle) || ! is_numeric($row[$key] ?? null)
|
||||
|| abs((float) $row[$key] - (float) $needle) > 0.000001)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -23,18 +23,29 @@
|
||||
$directoryCurrentPage = $directoryIsPaginated ? $directoryRows->currentPage() : 1;
|
||||
$directoryLastPage = $directoryIsPaginated ? $directoryRows->lastPage() : 1;
|
||||
$directoryPerPage = $directoryIsPaginated ? $directoryRows->perPage() : 0;
|
||||
$directoryPageKey = ($section['slug'] ?? '') . '/' . ($page['slug'] ?? '');
|
||||
// 页面配置在部分导航上下文中会省略父级 slug,优先使用当前路由参数避免功能开关失配。
|
||||
$directoryRouteSection = request()->route('section') ?: request()->segment(3) ?: ($section['slug'] ?? '');
|
||||
$directoryRoutePage = request()->route('page') ?: request()->segment(4) ?: ($page['slug'] ?? '');
|
||||
$directoryPageKey = (string) $directoryRouteSection . '/' . (string) $directoryRoutePage;
|
||||
$directoryUsesFieldQuery = in_array($directoryPageKey, [
|
||||
'company-management/basic-information',
|
||||
'store-management/basic-information',
|
||||
'trademark-management/information',
|
||||
'store-management/store-accounts',
|
||||
'store-management/store-report',
|
||||
'company-management/subject-operating-report',
|
||||
'company-management/company-cost-arrangement',
|
||||
'company-management/personnel-inventory',
|
||||
], true);
|
||||
$directoryQueryPageDescriptions = [
|
||||
'company-management/basic-information' => '公司主体、状态和登记资料的查询与导出',
|
||||
'store-management/basic-information' => '店铺主体、平台和经营资料的查询与导出',
|
||||
'trademark-management/information' => '商标主体、状态和登记资料的查询与导出',
|
||||
'store-management/store-accounts' => '店铺账号资料的查询与安全状态导出',
|
||||
'store-management/store-report' => '按店铺、主体和月份销量查询经营数据并导出',
|
||||
'company-management/subject-operating-report' => '按主体和收入月份查询经营汇总并导出',
|
||||
'company-management/company-cost-arrangement' => '按公司类型和发票类型查询成本安排字段',
|
||||
'company-management/personnel-inventory' => '按人员、职位和公司类型查询人员盘点字段',
|
||||
];
|
||||
@endphp
|
||||
|
||||
@ -372,6 +383,12 @@
|
||||
.directory-sort-link:hover { color: var(--vip-content-accent-dark, #856632); }
|
||||
.directory-sort-icon { width: 1rem; height: 1rem; color: var(--vip-content-muted, #595959); font-size: 1rem; line-height: 1; }
|
||||
.directory-sort-link[aria-current="true"] .directory-sort-icon { color: var(--vip-content-accent-dark, #856632); }
|
||||
.directory-header-menu { position: relative; display: inline-flex; align-items: center; gap: 0.15rem; }
|
||||
.directory-header-menu summary { display: inline-flex; align-items: center; cursor: pointer; list-style: none; }
|
||||
.directory-header-menu summary::-webkit-details-marker { display: none; }
|
||||
.directory-header-menu__panel { position: absolute; top: calc(100% + 0.35rem); left: 0; z-index: 9; display: grid; min-width: 8rem; gap: 0.2rem; padding: 0.35rem; border: 1px solid var(--vip-border, #d8d2c7); border-radius: 0.375rem; background: #fff; box-shadow: 0 4px 8px rgba(23, 23, 23, 0.10); }
|
||||
.directory-header-menu__panel a, .directory-header-menu__panel button { display: block; width: 100%; padding: 0.35rem 0.5rem; border: 0; border-radius: 0.25rem; background: transparent; color: var(--vip-content-ink, #171717); font: inherit; font-size: 0.6875rem; text-align: left; text-decoration: none; cursor: pointer; white-space: nowrap; }
|
||||
.directory-header-menu__panel a:hover, .directory-header-menu__panel button:hover, .directory-header-menu__panel a:focus, .directory-header-menu__panel button:focus { background: var(--vip-content-accent-soft, #f7f0de); outline: 0; }
|
||||
|
||||
.directory-table tbody tr:hover {
|
||||
background: #fbf8f0;
|
||||
@ -484,6 +501,11 @@
|
||||
<span class="material-icons-outlined" aria-hidden="true">error_outline</span>
|
||||
<span>{{ $upstreamError }}</span>
|
||||
</div>
|
||||
@elseif ($directoryTotal > 0 && in_array($directoryPageKey, ['store-management/store-accounts', 'store-management/store-report', 'company-management/subject-operating-report', 'company-management/company-cost-arrangement', 'company-management/personnel-inventory'], true))
|
||||
<div class="directory-table-notice" role="status">
|
||||
<span class="material-icons-outlined" aria-hidden="true">cloud_done</span>
|
||||
<span>已从共享业务镜像读取 {{ $directoryTotal }} 条数据,当前页面为只读展示。</span>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<header class="directory-table-head">
|
||||
@ -566,6 +588,10 @@
|
||||
'store-management/basic-information' => ['platform', 'store_status', 'withdrawal_account'],
|
||||
'trademark-management/information' => ['trademark_status'],
|
||||
'store-management/store-accounts' => ['account_type', 'password_configured'],
|
||||
'store-management/store-report' => ['status', 'can_change', 'company_type'],
|
||||
'company-management/subject-operating-report' => ['company_type', 'can_change_entity'],
|
||||
'company-management/company-cost-arrangement' => ['company_type', 'invoice_type'],
|
||||
'company-management/personnel-inventory' => ['company_type'],
|
||||
default => [],
|
||||
};
|
||||
$directoryColumnNames = collect($directoryFields)->pluck('name')->all();
|
||||
@ -620,6 +646,9 @@
|
||||
'taxpayer_type' => ['一般纳税人', '小规模纳税人', '个体工商户', '无税务登记', 'CORP'],
|
||||
'company_status' => ['正常', '已注销'],
|
||||
'company_type' => ['个体工商户', '个体户', '二级公司', '人力资源公司', '仓储公司', '其他', '合同公司', '品牌公司', '店铺-国标公司', '店铺-国标商标', '店铺-大店公司', '店铺-普通公司', '母公司', '资产型'],
|
||||
'status' => ['0' => '开店中', '3' => '关店中', '6' => '关店'],
|
||||
'can_change', 'can_change_entity' => ['是', '否'],
|
||||
'invoice_type' => ['普票', '专票'],
|
||||
default => [],
|
||||
};
|
||||
@endphp
|
||||
@ -635,7 +664,7 @@
|
||||
@endforeach
|
||||
<div class="directory-query-actions">
|
||||
<details class="directory-query-picker" data-directory-query-picker>
|
||||
<summary>勾选字段</summary>
|
||||
<summary>显示字段</summary>
|
||||
<div class="directory-query-picker__panel" role="group" aria-label="勾选展示字段">
|
||||
<span class="directory-query-picker__hint">勾选需要显示的表头,可重复调整。</span>
|
||||
@foreach ($directoryFields as $field)
|
||||
@ -664,7 +693,7 @@
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if (($page['slug'] ?? '') === 'store-report')
|
||||
@if (($page['slug'] ?? '') === 'store-report' && ! $directoryUsesFieldQuery)
|
||||
<form method="GET" action="{{ request()->url() }}" class="installation-filter-bar" aria-label="店铺维度报表筛选查询栏">
|
||||
@php
|
||||
$storeFilterTextFields = [
|
||||
@ -746,7 +775,7 @@
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
@if (!in_array(($section['slug'] ?? '') . '/' . ($page['slug'] ?? ''), ['company-management/basic-information', 'store-management/basic-information', 'trademark-management/information', 'store-management/store-accounts'], true))
|
||||
@if (! $directoryUsesFieldQuery)
|
||||
<div class="directory-table-tools" data-directory-tools>
|
||||
<label class="sr-only" for="directory-search-{{ $page['slug'] ?? 'page' }}">搜索当前表格</label>
|
||||
<input id="directory-search-{{ $page['slug'] ?? 'page' }}" type="search" placeholder="搜索当前页数据..." data-directory-search>
|
||||
@ -785,10 +814,17 @@
|
||||
$direction = $isSorted ? request('direction', 'asc') : null;
|
||||
$nextDirection = $isSorted && $direction === 'asc' ? 'desc' : 'asc';
|
||||
@endphp
|
||||
<a class="directory-sort-link" href="{{ request()->fullUrlWithQuery(['sort' => $field['name'], 'direction' => $nextDirection, 'page' => 1]) }}" aria-label="按{{ $field['label'] }}{{ $nextDirection === 'asc' ? '升序' : '降序' }}排列" aria-current="{{ $isSorted ? 'true' : 'false' }}">
|
||||
<div class="directory-header-menu" data-directory-header-menu="{{ $field['name'] }}">
|
||||
<span data-directory-header-label>{{ $field['label'] }}</span>
|
||||
<span class="material-icons-outlined directory-sort-icon" aria-hidden="true">{{ !$isSorted ? 'unfold_more' : ($direction === 'asc' ? 'arrow_upward' : 'arrow_downward') }}</span>
|
||||
</a>
|
||||
<details>
|
||||
<summary aria-label="{{ $field['label'] }}排序和字段操作"><span class="material-icons-outlined directory-sort-icon" aria-hidden="true">{{ !$isSorted ? 'unfold_more' : ($direction === 'asc' ? 'arrow_upward' : 'arrow_downward') }}</span></summary>
|
||||
<div class="directory-header-menu__panel">
|
||||
<a href="{{ request()->fullUrlWithQuery(['sort' => $field['name'], 'direction' => 'asc', 'page' => 1]) }}">升序</a>
|
||||
<a href="{{ request()->fullUrlWithQuery(['sort' => $field['name'], 'direction' => 'desc', 'page' => 1]) }}">降序</a>
|
||||
<button type="button" data-directory-header-hide="{{ $field['name'] }}">隐藏字段</button>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
@else
|
||||
{{ $field['label'] }}
|
||||
@endif
|
||||
@ -943,11 +979,13 @@
|
||||
input?.addEventListener('blur', () => window.setTimeout(() => { const list = field.querySelector('[data-directory-query-suggestions]'); if (list) list.hidden = true; }, 150));
|
||||
});
|
||||
const columnChecks = [...queryBar.querySelectorAll('[data-directory-query-column]')];
|
||||
const pickerSummary = queryBar.querySelector('[data-directory-query-picker] summary');
|
||||
const applyVisibleColumns = () => {
|
||||
const selected = columnChecks.filter((check) => check.checked).map((check) => check.value);
|
||||
table.querySelectorAll('[data-column-key]').forEach((cell) => {
|
||||
cell.classList.toggle('is-column-hidden', !selected.includes(cell.dataset.columnKey));
|
||||
});
|
||||
if (pickerSummary) pickerSummary.textContent = `显示字段 ${selected.length}/${columnChecks.length}`;
|
||||
let hiddenColumns = queryBar.querySelector(`input[form="${CSS.escape(queryBar.querySelector('form[id]')?.id || '')}"][name="columns"]`);
|
||||
if (!hiddenColumns) {
|
||||
hiddenColumns = document.createElement('input');
|
||||
@ -963,6 +1001,14 @@
|
||||
window.history.replaceState({}, '', url);
|
||||
};
|
||||
columnChecks.forEach((check) => check.addEventListener('change', applyVisibleColumns));
|
||||
section.querySelectorAll('[data-directory-header-hide]').forEach((button) => button.addEventListener('click', () => {
|
||||
const key = button.dataset.directoryHeaderHide;
|
||||
const check = columnChecks.find((item) => item.value === key);
|
||||
if (!check) return;
|
||||
check.checked = false;
|
||||
check.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
button.closest('details')?.removeAttribute('open');
|
||||
}));
|
||||
queryBar.querySelector('[data-directory-query-reset]')?.addEventListener('click', () => {
|
||||
const url = new URL(window.location.href);
|
||||
fields.forEach((field) => {
|
||||
|
||||
@ -72,6 +72,10 @@
|
||||
'title' => '内部流转报表', 'description' => '查看内部流转事项及办理状态',
|
||||
'columns' => [],
|
||||
],
|
||||
'internal-flow-report' => [
|
||||
'title' => '内部流转报表', 'description' => '查看内部流转事项及办理状态',
|
||||
'columns' => [],
|
||||
],
|
||||
];
|
||||
$definition = $definitions[$slug] ?? ['title' => $page['title'] ?? '业务页面', 'description' => '', 'columns' => [], 'rows' => []];
|
||||
@endphp
|
||||
|
||||
@ -311,7 +311,7 @@ class ManagementCrudTest extends FeatureTestCase
|
||||
->assertSeeText('既有只读上游')
|
||||
->assertSee('aria-label="公司基础信息查询条件"', false)
|
||||
->assertSeeText('相关人员')
|
||||
->assertSeeText('勾选字段')
|
||||
->assertSeeText('显示字段')
|
||||
->assertSeeText('导出 Excel')
|
||||
->assertSee('data-directory-query-export-field', false)
|
||||
->assertDontSee('<div class="directory-table-tools" data-directory-tools>', false)
|
||||
@ -560,10 +560,10 @@ class ManagementCrudTest extends FeatureTestCase
|
||||
'page' => 'store-report',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertSee('data-directory-field-picker', false)
|
||||
->assertSee('data-directory-export="visible"', false)
|
||||
->assertSee('data-directory-export-all="all-fields"', false)
|
||||
->assertSee('data-directory-reset', false)
|
||||
->assertSee('data-directory-query-picker', false)
|
||||
->assertSee('data-directory-query-export', false)
|
||||
->assertSeeText('显示字段')
|
||||
->assertSeeText('导出 Excel')
|
||||
->assertSee('position: sticky', false)
|
||||
->assertSee('overflow: auto', false);
|
||||
}
|
||||
|
||||
@ -104,6 +104,7 @@ class PrototypePageTest extends FeatureTestCase
|
||||
->assertSeeText('测试主体')
|
||||
->assertSeeText('共 1 条')
|
||||
->assertSeeText('已从共享业务镜像读取')
|
||||
->assertDontSee('<div class="directory-table-tools" data-directory-tools>', false)
|
||||
->assertDontSeeText('经营报表尚未同步。完成店铺主体和销量镜像接入后')
|
||||
->assertDontSeeText('经营报表共享镜像尚未完成同步。');
|
||||
}
|
||||
|
||||
@ -112,6 +112,20 @@ class StoreReportRepositoryTest extends TestCase
|
||||
$this->assertSame('公司表主体', $rows[0]['entity']);
|
||||
$this->assertSame('2026-07-01', $rows[0]['effective_at']);
|
||||
$this->assertSame('存续中', $rows[0]['invalid_at']);
|
||||
$this->assertCount(1, $this->repository->rows([['erp_store_name' => '店铺A']], 2026, ['sales_07' => '30.00']));
|
||||
$this->assertCount(0, $this->repository->rows([['erp_store_name' => '店铺A']], 2026, ['sales_07' => '130']));
|
||||
}
|
||||
|
||||
public function test_store_name_sort_is_accepted_and_applied(): void
|
||||
{
|
||||
request()->merge(['sort' => 'store_name', 'direction' => 'asc']);
|
||||
$filters = StoreReportRepository::requestFilters();
|
||||
$rows = $this->repository->rows([
|
||||
['erp_store_name' => '店铺A', 'store_name' => 'Z店'],
|
||||
['erp_store_name' => '店铺B', 'store_name' => 'A店'],
|
||||
], 2026, $filters);
|
||||
|
||||
$this->assertSame(['A店', 'Z店'], array_column($rows, 'store_name'));
|
||||
}
|
||||
|
||||
public function test_lifecycle_effective_date_before_default_is_preserved(): void
|
||||
@ -178,7 +192,7 @@ class StoreReportRepositoryTest extends TestCase
|
||||
$this->insertBill('店铺B', '2026-08-11 10:00:00', 40);
|
||||
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
|
||||
$mirror->shouldReceive('ready')->with('store-information.read')->andReturnTrue();
|
||||
$mirror->shouldReceive('allStores')->once()->andReturn([
|
||||
$mirror->shouldReceive('allStores')->times(3)->andReturn([
|
||||
['erp_store_name' => '店铺A', 'company_name' => '主体甲', 'store_status' => '0'],
|
||||
['erp_store_name' => '店铺B', 'company_name' => '主体甲', 'store_status' => '0'],
|
||||
]);
|
||||
@ -202,6 +216,11 @@ class StoreReportRepositoryTest extends TestCase
|
||||
$this->assertSame(10.0, $rows[0]->income_06);
|
||||
$this->assertSame(20.0, $rows[0]->income_07);
|
||||
$this->assertSame(70.0, $rows[0]->income_08);
|
||||
|
||||
request()->merge(['income_08' => '70.00']);
|
||||
$this->assertSame(1, $service->paginate('company-management', 'subject-operating-report', 50)->total());
|
||||
request()->merge(['income_08' => '71.00']);
|
||||
$this->assertSame(0, $service->paginate('company-management', 'subject-operating-report', 50)->total());
|
||||
}
|
||||
|
||||
private function insertBill(string $store, string $occurredAt, float $income): void
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user