From 780320f071961df74a66f552ae44c52049b16505 Mon Sep 17 00:00:00 2001 From: WuYongJie <549727955@qq.com> Date: Mon, 31 Aug 2026 21:51:30 +0800 Subject: [PATCH] fix: wire installation filters and protect child detail endpoint --- app/Business/UpstreamBusinessInformation.php | 2 +- app/Http/Controllers/Business/Pages.php | 6 +- .../InstallationDetailMirrorRepository.php | 52 +++++++++-- resources/views/business/pages/show.blade.php | 29 ++++-- tests/Feature/Business/ManagementCrudTest.php | 12 ++- ...InstallationDetailMirrorRepositoryTest.php | 90 +++++++++++++++++++ 6 files changed, 171 insertions(+), 20 deletions(-) create mode 100644 tests/Unit/EcommerceFinance/InstallationDetailMirrorRepositoryTest.php diff --git a/app/Business/UpstreamBusinessInformation.php b/app/Business/UpstreamBusinessInformation.php index 8062d660..1043fca7 100644 --- a/app/Business/UpstreamBusinessInformation.php +++ b/app/Business/UpstreamBusinessInformation.php @@ -48,7 +48,7 @@ class UpstreamBusinessInformation private function installationPage(int $page, int $perPage): array { - $payload = $this->installationMirror->paginate($page, $perPage); + $payload = $this->installationMirror->paginate($page, $perPage, InstallationDetailMirrorRepository::requestFilters()); if (! $payload['ready']) { throw new ServiceUnavailableHttpException(null, '安装明细镜像尚未完成首次同步'); } diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 45eab080..9c1d8dfe 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -165,12 +165,14 @@ class Pages extends Controller public function installationDetails(string $guid) { + $section = collect(SidebarPermissions::navigationFor(user()))->firstWhere('slug', 'financial-operations'); + abort_unless($section && collect($section['pages'] ?? [])->contains('slug', 'installation-details'), 403, '当前账号没有该页面权限。'); $payload = $this->installationDetails->details($guid); if (! $payload['ready']) { return response()->json(['message' => '安装明细镜像尚未完成首次同步'], 503); } - return response()->json(['data' => $payload['data']]); + return response()->json(['data' => $payload['data'], 'has_more' => $payload['has_more'] ?? false]); } protected function viewData( @@ -203,7 +205,7 @@ class Pages extends Controller 50, ); if ($section['slug'] === 'financial-operations' && $page['slug'] === 'installation-details') { - $installationSummary = $this->installationDetails->summary(); + $installationSummary = $this->installationDetails->summary(InstallationDetailMirrorRepository::requestFilters()); } } catch (ServiceUnavailableHttpException $exception) { // 首份快照尚未生成或某个来源暂时不可用时,保留页面、 diff --git a/modules/EcommerceFinance/Repositories/InstallationDetailMirrorRepository.php b/modules/EcommerceFinance/Repositories/InstallationDetailMirrorRepository.php index f7fdcf54..ea45b8d3 100644 --- a/modules/EcommerceFinance/Repositories/InstallationDetailMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/InstallationDetailMirrorRepository.php @@ -19,7 +19,7 @@ class InstallationDetailMirrorRepository } /** @return array{data:list>,total:int,ready:bool} */ - public function paginate(int $page, int $perPage): array + public function paginate(int $page, int $perPage, array $filters = []): array { $connection = $this->mirror->connection(); $dataset = $this->datasets->get(self::DATASET); @@ -37,6 +37,7 @@ class InstallationDetailMirrorRepository $query = $connection->table($dataset->targetTable() . ' AS v') ->joinSub($latest, 'latest', static fn ($join) => $join->on('latest.id', '=', 'v.Id')) ->select('v.*'); + $this->applyFilters($query, $filters, $items->targetTable()); $total = (clone $query)->count(); $rows = $query->orderByDesc('v.Id')->forPage(max($page, 1), $perPage)->get(); @@ -62,14 +63,14 @@ class InstallationDetailMirrorRepository $transactionNo = $connection->table($dataset->targetTable()) ->where('SourceGuid', $guid) - ->value('OrderNum'); + ->orderByDesc('Id')->value('OrderNum'); $rows = $connection->table($items->targetTable()) ->where('guid', $guid) ->orderBy('Id') - ->limit(100) + ->limit(101) ->get(); - return ['data' => $rows->map(fn (object $row): array => $this->mapItem((array) $row, $transactionNo))->all(), 'ready' => true]; + return ['data' => $rows->take(100)->map(fn (object $row): array => $this->mapItem((array) $row, $transactionNo))->all(), 'ready' => true, 'has_more' => $rows->count() > 100]; } private function ready($dataset, $items, string $connection): bool @@ -80,7 +81,7 @@ class InstallationDetailMirrorRepository } /** @return array{rows:int,requested:float,settled:float,paid:float} */ - public function summary(): array + public function summary(array $filters = []): array { $dataset = $this->datasets->get(self::DATASET); $connection = $this->mirror->connection(); @@ -90,13 +91,50 @@ class InstallationDetailMirrorRepository $latest = $connection->table($dataset->targetTable()) ->selectRaw('MAX(Id) AS id') ->groupBy('SourceGuid'); - $row = $connection->table($dataset->targetTable() . ' AS v') - ->joinSub($latest, 'latest', static fn ($join) => $join->on('latest.id', '=', 'v.Id')) + $query = $connection->table($dataset->targetTable() . ' AS v') + ->joinSub($latest, 'latest', static fn ($join) => $join->on('latest.id', '=', 'v.Id')); + $this->applyFilters($query, $filters, $this->datasets->get(self::ITEM_DATASET)->targetTable()); + $row = $query ->selectRaw('COUNT(*) AS row_count, COALESCE(SUM(v.installcost),0) AS requested, COALESCE(SUM(v.settleAmount),0) AS settled, COALESCE(SUM(v.acceptAmount),0) AS paid') ->first(); return ['rows' => (int) $row->row_count, 'requested' => (float) $row->requested, 'settled' => (float) $row->settled, 'paid' => (float) $row->paid]; } + public static function requestFilters(): array + { + $rules = []; + foreach (['created', 'customer_review', 'finance_review'] as $prefix) { + $rules[$prefix . '_from'] = ['nullable', 'date_format:Y-m-d']; + $rules[$prefix . '_to'] = ['nullable', 'date_format:Y-m-d']; + if (request()->filled($prefix . '_from')) $rules[$prefix . '_to'][] = 'after_or_equal:' . $prefix . '_from'; + } + foreach (['push_status','order_no','customer_review','finance_review','quote_status','arranged_status','fee_payer','completed_status','shop_name','delete_status','installation_company','installation_no','order_type','product_id'] as $key) { + $rules[$key] = ['nullable', 'string', 'max:191']; + } + foreach (['customer_review', 'finance_review', 'arranged_status', 'completed_status', 'delete_status'] as $key) $rules[$key][] = 'in:0,1'; + return validator(request()->only(array_keys($rules)), $rules)->validate(); + } + + private function applyFilters($query, array $filters, string $itemTable): void + { + foreach (['created'=>'SubmitTime', 'customer_review'=>'kfshTime', 'finance_review'=>'cwshTime'] as $prefix=>$column) { + if (!empty($filters[$prefix.'_from'])) $query->where('v.'.$column, '>=', $filters[$prefix.'_from']); + if (!empty($filters[$prefix.'_to'])) $query->where('v.'.$column, '<', \Illuminate\Support\Carbon::parse($filters[$prefix.'_to'])->addDay()->toDateString()); + } + foreach (['push_status'=>'apistatus','order_no'=>'OrderNum','customer_review'=>'kfsh','finance_review'=>'cwsh','quote_status'=>'BidStatus','fee_payer'=>'costtaker','completed_status'=>'installComplete','shop_name'=>'shop_name','delete_status'=>'Delete','installation_company'=>'installCompanyName','installation_no'=>'ordernum_id','order_type'=>'TypeDto'] as $key=>$column) { + if (isset($filters[$key]) && $filters[$key] !== '') $query->where('v.'.$column, $filters[$key]); + } + if (isset($filters['arranged_status']) && $filters['arranged_status'] !== '') { + $query->where('v.StatusId', $filters['arranged_status'] === '1' ? '>' : '<=', 0); + } + if (!empty($filters['product_id'])) { + $query->whereExists(function ($sub) use ($itemTable, $filters) { + $sub->selectRaw('1')->from($itemTable.' AS filter_item') + ->whereColumn('filter_item.guid', 'v.SourceGuid')->where('filter_item.shopproid', $filters['product_id']); + }); + } + } + /** @param array $row */ private function map(array $row): array { diff --git a/resources/views/business/pages/show.blade.php b/resources/views/business/pages/show.blade.php index 380ac5cf..1044b8a4 100644 --- a/resources/views/business/pages/show.blade.php +++ b/resources/views/business/pages/show.blade.php @@ -166,7 +166,8 @@ flex: 0 0 auto; } - .installation-filter-field input { + .installation-filter-field input, + .installation-filter-field select { min-width: 0; width: 100%; height: 2.1rem; @@ -187,6 +188,9 @@ @media (max-width: 1100px) { .installation-filter-bar { grid-template-columns: repeat(2, minmax(14rem, 1fr)); } } + @media (max-width: 650px) { + .installation-filter-bar { grid-template-columns: minmax(0, 1fr); padding: 1rem; } + } .biz-card-head { display: flex; @@ -581,14 +585,14 @@
@php $filterFields = [ - ['name' => 'created_from', 'label' => '创建日期:', 'type' => 'date'], ['name' => 'created_to', 'label' => '至:', 'type' => 'date'], + ['name' => 'created_from', 'label' => '提交日期:', 'type' => 'date'], ['name' => 'created_to', 'label' => '至:', 'type' => 'date'], ['name' => 'customer_review_from', 'label' => '客服审核时间:', 'type' => 'date'], ['name' => 'customer_review_to', 'label' => '至:', 'type' => 'date'], ['name' => 'finance_review_from', 'label' => '财务审核时间:', 'type' => 'date'], ['name' => 'finance_review_to', 'label' => '至:', 'type' => 'date'], ['name' => 'push_status', 'label' => '推送状态:'], ['name' => 'order_no', 'label' => '订单编号:'], - ['name' => 'customer_review', 'label' => '客服审核:'], ['name' => 'finance_review', 'label' => '财务审核:'], - ['name' => 'quote_status', 'label' => '报价状态:'], ['name' => 'arranged_status', 'label' => '是否安排:'], - ['name' => 'fee_payer', 'label' => '安装费承担方:'], ['name' => 'completed_status', 'label' => '是否完工:'], - ['name' => 'shop_name', 'label' => '选择店铺:'], ['name' => 'delete_status', 'label' => '选择删除:'], + ['name' => 'customer_review', 'label' => '客服审核:', 'options' => ['0'=>'未审核','1'=>'已审核']], ['name' => 'finance_review', 'label' => '财务审核:', 'options' => ['0'=>'未审核','1'=>'已审核']], + ['name' => 'quote_status', 'label' => '报价状态:'], ['name' => 'arranged_status', 'label' => '是否安排:', 'options' => ['0'=>'未安排','1'=>'已安排']], + ['name' => 'fee_payer', 'label' => '安装费承担方:'], ['name' => 'completed_status', 'label' => '是否完工:', 'options' => ['0'=>'未完工','1'=>'已完工']], + ['name' => 'shop_name', 'label' => '选择店铺:'], ['name' => 'delete_status', 'label' => '选择删除:', 'options' => ['0'=>'未删除','1'=>'已删除']], ['name' => 'installation_company', 'label' => '安装公司:'], ['name' => 'installation_no', 'label' => '安装单号:'], ['name' => 'order_type', 'label' => '订单类型:'], ['name' => 'product_id', 'label' => '商品ID:'], ]; @@ -596,7 +600,11 @@ @foreach ($filterFields as $filter) @endforeach
@@ -664,7 +672,7 @@ @forelse ($rows as $row) - + @if ($page['slug'] === 'installation-details' && $row->related_guid)