法人名称按账户数量排序 #967

Merged
shanshi merged 2 commits from feature/shanshi-legal-representative-count-sort into main 2026-11-06 17:57:22 +08:00
3 changed files with 121 additions and 36 deletions

View File

@ -8,6 +8,7 @@ use App\Utilities\FinanceLedgerAccountType;
use App\Utilities\FinanceLedgerAccess;
use App\Utilities\FinanceLedgerIndependentAccounts;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Carbon;
@ -609,6 +610,9 @@ class FinanceLedger extends Controller
->reorder()
->select('accounts.company_name')
->pluck('accounts.company_name');
$legalRepresentativeCounts = $legalRepresentativeCountSource->countBy(
static fn ($companyName): string => trim((string) ($legalRepresentatives[trim((string) $companyName)] ?? '')),
);
$summaryQuery = clone $query;
$summary = [
'count' => (clone $query)->count(),
@ -624,19 +628,8 @@ class FinanceLedger extends Controller
$sortField = in_array($sortField, $headerFilterColumns, true) ? $sortField : '';
$sortDirection = strtolower((string) $request->query('keep_direction', 'asc'));
$sortDirection = $sortDirection === 'desc' ? 'desc' : 'asc';
if ($sortField !== '') {
if ($sortField === 'legal_representative') {
if ($connection->getSchemaBuilder()->hasTable('ops_fys_company_info')) {
$legalRepresentativeOrder = $connection->table('ops_fys_company_info')
->select('legalRepresentative')
->whereColumn('businessLicenseName', 'accounts.company_name')
->orderByDesc('Id')
->limit(1);
$query->orderBy($legalRepresentativeOrder, $sortDirection);
} else {
$query->orderBy('accounts.company_name', $sortDirection);
}
} elseif ($sortField === 'account_type') {
if ($sortField !== '' && $sortField !== 'legal_representative') {
if ($sortField === 'account_type') {
$query->orderByRaw(
'CASE WHEN type_override_id IS NOT NULL THEN effective_account_type'
. ' ELSE sort_account_type END ' . $sortDirection,
@ -674,27 +667,75 @@ class FinanceLedger extends Controller
}
}
$rows = $query
$hydrateRows = static function ($rows) use ($legalRepresentatives, $legalRepresentativeCounts): void {
$rows->transform(static function ($row) use ($legalRepresentatives, $legalRepresentativeCounts) {
$row->account_status = FinanceLedgerAccountStatus::normalize($row->effective_account_status ?? null);
if ($row->type_override_id !== null) {
$row->account_type = (string) ($row->effective_account_type ?? '');
}
$row->todo_current_progress = $row->todo_current_progress ?: 'not_started';
$row->legal_representative = $legalRepresentatives[trim((string) $row->company_name)] ?? null;
$row->legal_representative_count = (int) ($legalRepresentativeCounts[
trim((string) $row->legal_representative)
] ?? 0);
return $row;
});
};
$orderedQuery = $query
->orderBy('accounts.company_name')
->orderBy('accounts.account_short')
->paginate(50)
->appends($filters)
->appends(array_filter([
->orderBy('accounts.account_short');
if ($sortField === 'legal_representative') {
$allRows = $orderedQuery->get();
$hydrateRows($allRows);
$allRows = $allRows->sort(static function ($left, $right) use ($sortDirection): int {
$countOrder = (int) $left->legal_representative_count <=> (int) $right->legal_representative_count;
if ($sortDirection === 'desc') {
$countOrder *= -1;
}
if ($countOrder !== 0) {
return $countOrder;
}
$nameOrder = strnatcasecmp(
trim((string) $left->legal_representative),
trim((string) $right->legal_representative),
);
if ($nameOrder !== 0) {
return $nameOrder;
}
$companyOrder = strnatcasecmp((string) $left->company_name, (string) $right->company_name);
return $companyOrder !== 0
? $companyOrder
: strnatcasecmp((string) $left->account_short, (string) $right->account_short);
})->values();
$lastPage = max(1, (int) ceil($allRows->count() / 50));
$page = min($lastPage, max(1, (int) $request->query('page', 1)));
$rows = new LengthAwarePaginator(
$allRows->forPage($page, 50)->values(),
$allRows->count(),
50,
$page,
['path' => $request->url(), 'query' => $request->query()],
);
$rows->appends($filters)->appends([
'keep_sort' => $sortField,
'keep_direction' => $sortField !== '' ? $sortDirection : '',
]))
->withQueryString();
$rows->getCollection()->transform(static function ($row) use ($legalRepresentatives) {
$row->account_status = FinanceLedgerAccountStatus::normalize($row->effective_account_status ?? null);
if ($row->type_override_id !== null) {
$row->account_type = (string) ($row->effective_account_type ?? '');
}
$row->todo_current_progress = $row->todo_current_progress ?: 'not_started';
$row->legal_representative = $legalRepresentatives[trim((string) $row->company_name)] ?? null;
return $row;
});
])->withQueryString();
} else {
$rows = $orderedQuery
->paginate(50)
->appends($filters)
->appends(array_filter([
'keep_sort' => $sortField,
'keep_direction' => $sortField !== '' ? $sortDirection : '',
]))
->withQueryString();
$hydrateRows($rows->getCollection());
}
$ownerOptions = self::UNRETAINED_OWNER_OPTIONS;
$headerOptionValues = static function (iterable $values, bool $includeEmpty = false): array {
@ -714,9 +755,6 @@ class FinanceLedger extends Controller
};
$hasEmpty = static fn (string $column): bool => $headerOptionRows
->contains(static fn ($row): bool => trim((string) ($row->{$column} ?? '')) === '');
$legalRepresentativeCounts = $legalRepresentativeCountSource->countBy(
static fn ($companyName): string => trim((string) ($legalRepresentatives[trim((string) $companyName)] ?? '')),
);
$legalRepresentativeOptions = [];
if (($legalRepresentativeCounts[''] ?? 0) > 0) {
$legalRepresentativeOptions[] = '未填写(' . $legalRepresentativeCounts[''] . ')';

View File

@ -5,7 +5,7 @@
$keepHeaderColumns = [
['column' => 'company_name', 'label' => '公司名称', 'filterType' => 'searchable-enum'],
['column' => 'account_short', 'label' => '账户简称', 'filterType' => 'searchable-enum'],
['column' => 'legal_representative', 'label' => '法人名称', 'filterType' => 'searchable-enum'],
['column' => 'legal_representative', 'label' => '法人名称 / 数量', 'filterType' => 'searchable-enum'],
['column' => 'account_type', 'label' => '账户类型', 'filterType' => 'searchable-enum'],
['column' => 'account_status', 'label' => '账户状态', 'filterType' => 'searchable-enum'],
['column' => 'account_no', 'label' => '账号', 'filterType' => 'searchable-enum'],
@ -333,7 +333,7 @@
{{ $row->company_name ?: '未填写' }}
</td>
<td>{{ $row->account_short ?: '未填写' }}</td>
<td>{{ $row->legal_representative ?: '未填写' }}</td>
<td data-keep-legal-representative="{{ $row->legal_representative }}" data-keep-legal-representative-count="{{ $row->legal_representative_count }}">{{ $row->legal_representative ?: '未填写' }}({{ $row->legal_representative_count }})</td>
<td>{{ $row->account_type ?: '未填写' }}</td>
<td><span class="keep-task-status is-{{ $row->account_status }}">{{ $accountStatusOptions[$row->account_status] ?? '正常' }}</span></td>
<td>{{ $row->account_no ?: '未填写' }}</td>

View File

@ -391,6 +391,53 @@ class FinanceLedgerAccountKeepTodoTest extends FeatureTestCase
->assertDontSee('data-keep-account="法人计数甲二"', false);
}
public function test_legal_representative_sort_uses_account_group_counts(): void
{
$connection = DB::connection('business_mirror');
$connection->table('fin_accounts')->insert([
['company_name' => '法人排序公司甲', 'account_short' => '法人排序甲一', 'account_should_keep' => '否'],
['company_name' => '法人排序公司乙', 'account_short' => '法人排序甲二', 'account_should_keep' => '否'],
['company_name' => '法人排序公司丙', 'account_short' => '法人排序甲三', 'account_should_keep' => '否'],
['company_name' => '法人排序公司丁', 'account_short' => '法人排序乙一', 'account_should_keep' => '否'],
['company_name' => '法人排序公司戊', 'account_short' => '法人排序丙一', 'account_should_keep' => '否'],
]);
$connection->table('ops_fys_company_info')->insert([
['Id' => 41, 'businessLicenseName' => '法人排序公司甲', 'legalRepresentative' => '赵法人'],
['Id' => 42, 'businessLicenseName' => '法人排序公司乙', 'legalRepresentative' => '赵法人'],
['Id' => 43, 'businessLicenseName' => '法人排序公司丙', 'legalRepresentative' => '赵法人'],
['Id' => 44, 'businessLicenseName' => '法人排序公司丁', 'legalRepresentative' => '李法人'],
['Id' => 45, 'businessLicenseName' => '法人排序公司戊', 'legalRepresentative' => '王法人'],
]);
$rows = function (string $direction): array {
$content = $this->loginAs()->get($this->keepTasksUrl([
'keep_sort' => 'legal_representative',
'keep_direction' => $direction,
]))->assertOk()->getContent();
preg_match_all('/<tr data-keep-account="([^"]+)">(.*?)<\/tr>/su', $content, $matches, PREG_SET_ORDER);
return array_map(static function (array $match): array {
preg_match('/data-keep-legal-representative-count="(\d+)"/u', $match[2], $count);
return [(string) $match[1], (int) ($count[1] ?? 0)];
}, $matches);
};
$descending = $rows('desc');
$this->assertSame([3, 3, 3], array_column(array_slice($descending, 0, 3), 1));
$this->assertSame([1, 1], array_column(array_slice($descending, 3, 2), 1));
$ascending = $rows('asc');
$this->assertSame([1, 1], array_column(array_slice($ascending, 0, 2), 1));
$this->assertSame([3, 3, 3], array_column(array_slice($ascending, 2, 3), 1));
$this->loginAs()->get($this->keepTasksUrl([
'keep_sort' => 'legal_representative',
'keep_direction' => 'desc',
]))->assertOk()
->assertSee('data-keep-legal-representative-count="3"', false)
->assertSee('赵法人(3)');
}
public function test_keep_tasks_header_filters_apply_with_typeahead_options(): void
{
$connection = DB::connection('business_mirror');