From b1414a662212c522912700cb6b188fe0481f9ce7 Mon Sep 17 00:00:00 2001 From: limeng <1131177064@qq.com> Date: Thu, 15 Oct 2026 14:03:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BF=AB=E9=80=92=E5=BA=94=E4=BB=98=EF=BC=9A?= =?UTF-8?q?=E6=B8=85=E7=A9=BA=E8=BF=90=E8=B4=B9=E5=AF=B9=E8=B4=A6=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=B9=B6=E5=81=9C=E7=94=A8=E9=94=99=E8=AF=AF=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Business/Pages.php | 85 +--------- .../freight-reconciliation.blade.php | 145 +++--------------- .../CourierFreightReconciliationPageTest.php | 102 ++++-------- tests/Feature/Business/PagesTest.php | 38 +---- 4 files changed, 67 insertions(+), 303 deletions(-) diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 13a8e06b..36330db2 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -48,7 +48,6 @@ use Modules\EcommerceFinance\Repositories\AlibabaAccountBalanceRepository; use Modules\EcommerceFinance\Repositories\AlibabaBillRepository; use Modules\EcommerceFinance\View\BillOffsetPageData; use Modules\EcommerceFinance\Repositories\CourierInformationMirrorRepository; -use Modules\EcommerceFinance\Repositories\CourierFreightReconciliationMirrorRepository; use Modules\EcommerceFinance\Repositories\CourierSurchargeSettlementMirrorRepository; use Modules\EcommerceFinance\Repositories\ShippingOrderRepository; use Modules\EcommerceFinance\Repositories\ThirdPartyWarehouseStorageMirrorRepository; @@ -420,7 +419,6 @@ class Pages extends Controller private readonly ProductCatalogMirrorRepository $productCatalog, private readonly ProductPriceMirrorRepository $productPrices, private readonly CourierInformationMirrorRepository $courierInformation, - private readonly CourierFreightReconciliationMirrorRepository $courierFreightReconciliations, private readonly ThirdPartyWarehouseStorageMirrorRepository $thirdPartyWarehouseStorage, private readonly RawBoardPayablesRepository $rawBoardPayables, private readonly DingPayableWorkflowMirrorRepository $dingPayableWorkflows, @@ -574,18 +572,11 @@ class Pages extends Controller } if ($section === 'courier-payables' && $page === 'freight-reconciliation' && request()->boolean('courier_summary')) { - try { - $summary = $this->courierFreightReconciliations->requestSummary($this->freightReconciliationFilters()); - $response = response()->json(['data' => $summary], $summary === null ? 202 : 200, [ - 'Cache-Control' => 'private, no-store', 'X-Accel-Buffering' => 'no', - ]); - // Explicit framing lets clients finish reading before the - // application termination callback computes the aggregate. - return $response->header('Content-Length', strlen($response->getContent())); - } catch (Throwable $exception) { - report($exception); - return response()->json(['message' => '完整汇总暂时无法计算,明细仍可正常查看。'], 503, ['Cache-Control' => 'private, no-store']); - } + return response()->json([ + 'data' => ['count' => 0, 'freight' => 0], + 'status' => 'disabled', + 'message' => '原数据源已停用,本页面不再展示原运费对账数据。', + ], 200, ['Cache-Control' => 'private, no-store']); } return view('business.pages.show', $this->viewData( @@ -4660,75 +4651,11 @@ class Pages extends Controller return $text !== '' && is_numeric($text) ? (float) $text : null; } - private function freightReconciliationFilters(): array - { - $request = request(); - $date = static function (mixed $value): string { - $value = trim((string) $value); - return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1 ? $value : ''; - }; - return [ - 'express_no' => mb_substr(trim((string) $request->query('express_no', '')), 0, 80), - 'express_name' => mb_substr(trim((string) $request->query('express_name', '')), 0, 80), - 'month' => mb_substr(trim((string) $request->query('month', '')), 0, 20), - 'status' => mb_substr(trim((string) $request->query('status', '')), 0, 40), - 'modified_from' => $date($request->query('modified_from', '')), - 'modified_to' => $date($request->query('modified_to', '')), - ]; - } - protected function contentForFreightReconciliation(): array { - $request = request(); - $filters = $this->freightReconciliationFilters(); - $page = max((int) $request->query('page', 1), 1); - $limit = min(max((int) setting('default.list_limit', 25), 1), 100); - $rows = new Paginator([], $limit, $page, [ - 'path' => $request->url(), - 'query' => $request->except('page'), - ]); - $state = null; - $sync = ['code' => 'unavailable', 'last_succeeded_at' => null, 'lag_minutes' => null]; - $summary = ['count' => null, 'freight' => null]; - $error = null; - $pending = false; - - try { - if (! $this->courierFreightReconciliations->available()) { - $error = '共享业务镜像尚未建立,运费对账暂时无法读取。'; - } elseif (! $this->courierFreightReconciliations->ready()) { - $state = $this->courierFreightReconciliations->state(); - $sync = $this->courierFreightReconciliations->syncStatus($state); - $pending = true; - $error = '运费对账镜像正在完成首次同步,完成前不会把未同步数据误判为无数据。'; - } else { - $result = $this->courierFreightReconciliations->paginate($filters, $page, $limit); - $rows = new Paginator( - Collection::make($result['data'] ?? []), - $limit, - $page, - ['path' => $request->url(), 'query' => $request->except('page')], - ); - $rows->hasMorePagesWhen((bool) ($result['has_more'] ?? false)); - $state = $result['state'] ?? null; - $sync = $result['sync'] ?? $sync; - $summary = $result['summary'] ?? $summary; - } - } catch (Throwable $exception) { - report($exception); - $error = '运费对账数据暂时无法读取,请稍后重试。'; - } - + // 原来源口径不适用;保留入口,但不读取明细、同步状态或汇总。 return [ 'view' => 'business.pages.courier-payables.freight-reconciliation', - 'freightReconciliations' => $rows, - 'freightReconciliationFilters' => $filters, - 'freightReconciliationSummary' => $summary, - 'freightReconciliationState' => $state, - 'freightReconciliationSync' => $sync, - 'freightReconciliationError' => $error, - 'freightReconciliationPending' => $pending, - 'freightReconciliationSummaryUrl' => $error === null ? $request->fullUrlWithQuery(['courier_summary' => 1, 'page' => null]) : null, ]; } diff --git a/resources/views/business/pages/courier-payables/freight-reconciliation.blade.php b/resources/views/business/pages/courier-payables/freight-reconciliation.blade.php index 37b01b31..63cfcf9b 100644 --- a/resources/views/business/pages/courier-payables/freight-reconciliation.blade.php +++ b/resources/views/business/pages/courier-payables/freight-reconciliation.blade.php @@ -1,131 +1,32 @@ -@php - $filters = $freightReconciliationFilters ?? []; - $summary = $freightReconciliationSummary ?? []; - $summaryReady = isset($summary['count'], $summary['freight']); - $sync = $freightReconciliationSync ?? ['code' => 'unavailable', 'last_succeeded_at' => null, 'lag_minutes' => null]; - $statusLabels = ['fresh' => '数据新鲜', 'stale' => '数据可能滞后', 'syncing' => '同步中', 'initializing' => '首次同步中', 'pending' => '等待同步', 'failed' => '同步失败', 'initial_failed' => '首次同步失败', 'unavailable' => '镜像不可用']; - $money = static fn (mixed $value): string => number_format((float) ($value ?? 0), 2); - $number = static fn (mixed $value): string => $value === null || $value === '' ? '-' : rtrim(rtrim(number_format((float) $value, 4, '.', ''), '0'), '.'); -@endphp -
- +
+

原数据源已停用

+

原数据来源不适用于运费对账,已清空本页面展示;接入正确数据源前,不再展示原明细或运费汇总。

+
-
-
-

{{ $page['title'] ?? '运费对账' }}

-

数据来源:JianSun · dbo.jst_order_express_details + dbo.Jst_SendData_View_extension + dbo.jst_salesOutboundInfo · 共享业务镜像

+
+
+
记录数
+
0
-
- {{ $statusLabels[$sync['code'] ?? 'unavailable'] ?? '镜像状态未知' }} - @if (!empty($sync['last_succeeded_at'])) 最近同步 {{ $sync['last_succeeded_at'] }} @endif +
+
运费合计
+
¥ 0.00
- @if (!empty($freightReconciliationError)) -
{{ $freightReconciliationError }}
- @endif - -
-
-
记录数
{{ $summaryReady ? number_format((int) $summary['count']) : '—' }}
-
运费合计
{{ $summaryReady ? '¥ ' . $money($summary['freight']) : '—' }}
+
+
+ + + @foreach (['快递单号','商品ID','发货时间','交易订单号','省','市','区','条形码','快递名称1','快递名称2','数量','商品名称','平台','店铺名称','仓库','体积(cm3)','供应商','长(cm)','宽(cm)','高(cm)','运费','对账重量(kg)','对账体积(cm3)'] as $heading) + + @endforeach + + + + +
{{ $heading }}
本页面数据已清空,等待接入正确的运费对账数据源。
-

@if ($summaryReady) 完整筛选汇总{{ !empty($summary['computed_at']) ? ' · ' . $summary['computed_at'] : '' }} @elseif (!empty($freightReconciliationSummaryUrl)) 正在计算完整筛选汇总,明细可继续查看… @else 同步完成后提供汇总 @endif

-
- -
-
- @foreach ([ - ['name' => 'express_no', 'label' => '快递单号', 'placeholder' => '包含匹配'], - ['name' => 'express_name', 'label' => '快递名称', 'placeholder' => '包含匹配'], - ['name' => 'month', 'label' => '月份', 'placeholder' => '如 2026-08'], - ['name' => 'status', 'label' => '物流状态', 'placeholder' => '精确匹配'], - ] as $field) - - @endforeach - - -
重置
-
-
- -
-
- @foreach (['快递单号','商品ID','发货时间','交易订单号','省','市','区','条形码','快递名称1','快递名称2','数量','商品名称','平台','店铺名称','仓库','体积(cm3)','供应商','长(cm)','宽(cm)','高(cm)','运费','对账重量(kg)','对账体积(cm3)'] as $heading) - - @endforeach - - @forelse ($freightReconciliations ?? [] as $row) - - - - - - - - - - @foreach (['length','width','height','freight','reconciliation_weight','reconciliation_volume'] as $numberField) - - @endforeach - - @empty - - @endforelse -
{{ $heading }}
{{ $row['waybill'] ?? '-' }}{{ $row['product_id'] ?? '-' }}{{ $row['send_time'] ?? '-' }}{{ $row['transaction_order_no'] ?? '-' }}{{ $row['province'] ?? '-' }}{{ $row['city'] ?? '-' }}{{ $row['district'] ?? '-' }}{{ $row['barcode'] ?? '-' }}{{ $row['express_name'] ?? '-' }}{{ $row['express_name_2'] ?? '-' }}{{ $row['quantity'] ?? '-' }}{{ $row['product_name'] ?? '-' }}{{ $row['platform'] ?? '-' }}{{ $row['shop_name'] ?? '-' }}{{ $row['warehouse'] ?? '-' }}{{ $number($row['volume'] ?? null) }}{{ $row['supplier'] ?? '-' }}{{ $row[$numberField] === null ? '-' : ($numberField === 'freight' ? $money($row[$numberField]) : $number($row[$numberField])) }}
@if ($freightReconciliationPending ?? false) 正在等待物流明细镜像首次同步完成… @else 暂无符合条件的运费对账数据 @endif
- @if ($freightReconciliations ?? null) -
@if ($summaryReady) 共 {{ number_format((int) $summary['count']) }} 条 · 共 {{ (int) ceil($summary['count'] / $freightReconciliations->perPage()) }} 页 @else 总条数 / 总页数计算中 @endif · 当前第 {{ $freightReconciliations->currentPage() }} 页 · 本页 {{ $freightReconciliations->count() }} 条
{{ $freightReconciliations->links() }}
- @endif
- - diff --git a/tests/Feature/Business/CourierFreightReconciliationPageTest.php b/tests/Feature/Business/CourierFreightReconciliationPageTest.php index e027dd22..9d3082a9 100644 --- a/tests/Feature/Business/CourierFreightReconciliationPageTest.php +++ b/tests/Feature/Business/CourierFreightReconciliationPageTest.php @@ -3,103 +3,65 @@ namespace Tests\Feature\Business; use App\Models\Auth\Permission; -use Illuminate\Testing\TestResponse; use Modules\EcommerceFinance\Repositories\CourierFreightReconciliationMirrorRepository; use Tests\Feature\FeatureTestCase; class CourierFreightReconciliationPageTest extends FeatureTestCase { - protected function renderFreightPage(bool $summaryReady = true): TestResponse + protected function setUp(): void { + parent::setUp(); + $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = \Mockery::mock(CourierFreightReconciliationMirrorRepository::class); - $repository->shouldReceive('available')->once()->andReturnTrue(); - $repository->shouldReceive('ready')->once()->andReturnTrue(); - $repository->shouldReceive('paginate')->once()->andReturn([ - 'data' => [[ - 'waybill' => '00123456789012345678', 'product_id' => 'ITEM-1', - 'send_time' => '2026-09-01 12:00:00', 'transaction_order_no' => 'SO-1', - 'province' => '江苏省', 'city' => '常州市', 'district' => '武进区', - 'barcode' => 'SKU-1', 'express_name' => '测试快递', 'express_name_2' => '测试网点', - 'quantity' => 2, 'product_name' => '验收商品', 'platform' => '测试平台', - 'shop_name' => '验收店铺', 'warehouse' => '验收仓库', 'volume' => 7161, - 'supplier' => '验收供应商', 'length' => 11, 'width' => 21, 'height' => 31, - 'freight' => 8.8, 'reconciliation_weight' => 0.7, 'reconciliation_volume' => 7161, - ]], - 'total' => $summaryReady ? 1 : null, 'has_more' => false, - 'summary' => $summaryReady ? ['count' => 1, 'freight' => 8.8] : null, - 'sync' => ['code' => 'fresh', 'last_succeeded_at' => '2026-09-09 10:00:00', 'lag_minutes' => 0], - ]); + foreach (['available', 'ready', 'state', 'syncStatus', 'paginate', 'requestSummary'] as $method) { + $repository->shouldNotReceive($method); + } $this->app->instance(CourierFreightReconciliationMirrorRepository::class, $repository); - - return $this->loginAs()->get(route('business.pages.show', [ - 'section' => 'courier-payables', 'page' => 'freight-reconciliation', - ])); } - public function test_rendered_values_match_their_column_headings(): void + public function test_page_stays_empty_with_old_filters_and_page_number(): void { - $response = $this->renderFreightPage()->assertOk(); + $response = $this->loginAs()->get(route('business.pages.show', [ + 'section' => 'courier-payables', 'page' => 'freight-reconciliation', + 'express_no' => 'OLD-WAYBILL', 'month' => '2026-09', + ]) . '&page=2'); + + $response->assertOk() + ->assertSeeText('原数据源已停用') + ->assertSeeText('本页面数据已清空,等待接入正确的运费对账数据源。') + ->assertSee('data-summary-count>0', false) + ->assertSee('data-summary-freight>¥ 0.00', false) + ->assertDontSeeText('OLD-WAYBILL') + ->assertDontSeeText('最近同步') + ->assertDontSeeText('正在计算完整筛选汇总') + ->assertDontSee('data-summary-url', false) + ->assertDontSee('jst_order_express_details', false); + $document = new \DOMDocument(); $previous = libxml_use_internal_errors(true); $document->loadHTML('' . $response->getContent()); libxml_clear_errors(); libxml_use_internal_errors($previous); $xpath = new \DOMXPath($document); - $headers = $xpath->query('//table[contains(@class,"recon-table")]/thead/tr/th'); - $values = $xpath->query('//table[contains(@class,"recon-table")]/tbody/tr[1]/td'); - $this->assertCount(23, $headers); - $this->assertCount(23, $values); - $row = []; - foreach ($headers as $index => $header) $row[trim($header->textContent)] = trim($values->item($index)->textContent); - $this->assertSame('7161', $row['体积(cm3)']); - $this->assertSame('验收供应商', $row['供应商']); - $this->assertSame('11', $row['长(cm)']); - $this->assertSame('21', $row['宽(cm)']); - $this->assertSame('31', $row['高(cm)']); - $this->assertSame('8.80', $row['运费']); - $this->assertSame('00123456789012345678', $row['快递单号']); + $this->assertCount(23, $xpath->query('//table[contains(@class,"recon-table")]/thead/tr/th')); + $this->assertCount(1, $xpath->query('//table[contains(@class,"recon-table")]/tbody/tr/td[@colspan="23"]')); } - public function test_rows_remain_available_while_the_complete_summary_is_pending(): void + public function test_old_summary_endpoint_returns_zero_without_querying_the_disabled_source(): void { - $this->renderFreightPage(false)->assertOk() - ->assertSeeText('验收商品') - ->assertSeeText('正在计算完整筛选汇总') - ->assertSee('data-summary-ready="false"', false) - ->assertSee('data-summary-count>—', false); - } - - public function test_summary_request_keeps_filters_and_returns_only_the_summary(): void - { - $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); - $repository = \Mockery::mock(CourierFreightReconciliationMirrorRepository::class); - $repository->shouldReceive('requestSummary')->once() - ->withArgs(fn (array $filters) => $filters['month'] === '2026-09' && $filters['express_no'] === 'YT') - ->andReturn(['count' => 48, 'freight' => 8.8, 'computed_at' => '2026-09-09 10:00:00']); - $this->app->instance(CourierFreightReconciliationMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'freight-reconciliation', - 'courier_summary' => 1, 'month' => '2026-09', 'express_no' => ' YT ', - ]))->assertOk()->assertJsonPath('data.count', 48)->assertJsonPath('data.freight', 8.8); - } - - public function test_busy_summary_returns_pending_without_starting_another_calculation(): void - { - $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); - $repository = \Mockery::mock(CourierFreightReconciliationMirrorRepository::class); - $repository->shouldReceive('requestSummary')->once()->andReturnNull(); - $this->app->instance(CourierFreightReconciliationMirrorRepository::class, $repository); - $this->loginAs()->get(route('business.pages.show', [ - 'section' => 'courier-payables', 'page' => 'freight-reconciliation', 'courier_summary' => 1, - ]))->assertStatus(202)->assertJsonPath('data', null)->assertHeader('Content-Length', '13')->assertHeader('X-Accel-Buffering', 'no'); + 'courier_summary' => 1, 'month' => '2026-09', 'express_no' => 'OLD-WAYBILL', + ]))->assertOk()->assertExactJson([ + 'data' => ['count' => 0, 'freight' => 0], + 'status' => 'disabled', + 'message' => '原数据源已停用,本页面不再展示原运费对账数据。', + ])->assertHeader('Cache-Control', 'no-store, private'); } public function test_summary_is_not_available_without_login(): void { - $repository = \Mockery::mock(CourierFreightReconciliationMirrorRepository::class); - $repository->shouldNotReceive('requestSummary'); - $this->app->instance(CourierFreightReconciliationMirrorRepository::class, $repository); $this->withExceptionHandling()->get(route('business.pages.show', [ 'company_id' => $this->company->id, 'section' => 'courier-payables', 'page' => 'freight-reconciliation', 'courier_summary' => 1, diff --git a/tests/Feature/Business/PagesTest.php b/tests/Feature/Business/PagesTest.php index 8435f2f6..faab5026 100644 --- a/tests/Feature/Business/PagesTest.php +++ b/tests/Feature/Business/PagesTest.php @@ -396,56 +396,30 @@ class PagesTest extends FeatureTestCase ->assertDontSeeText('已从共享业务镜像读取'); } - public function testFreightReconciliationPageRendersSharedMirrorRowsAndSummary(): void + public function testFreightReconciliationPageIsEmptyAfterDisablingTheOldSource(): void { $this->user->attachPermission( Permission::where('name', 'read-admin-panel')->firstOrFail() ); $repository = Mockery::mock(CourierFreightReconciliationMirrorRepository::class); - $repository->shouldReceive('available')->once()->andReturnTrue(); - $repository->shouldReceive('ready')->once()->andReturnTrue(); - $repository->shouldReceive('paginate')->once()->with( - Mockery::on(static fn (array $filters): bool => $filters['express_no'] === 'FIXTURE'), - 1, - Mockery::type('int'), - )->andReturn([ - 'data' => [[ - 'detail_id' => 1, - 'waybill' => 'FIXTURE-WAYBILL', 'product_id' => 'ITEM-1', - 'send_time' => '2026-08-01 09:00:00', 'transaction_order_no' => 'SO-1', - 'province' => '江苏省', 'city' => '常州市', 'district' => '武进区', 'barcode' => 'SKU-1', - 'express_name' => '测试快递', 'express_name_2' => '测试快递', 'quantity' => 1, - 'product_name' => '测试商品', 'platform' => '淘宝天猫', 'shop_name' => '测试店铺', - 'warehouse' => '测试仓库', 'volume' => 1.2, 'supplier' => '测试供应商', - 'length' => 1.0, 'width' => 2.0, 'height' => 3.0, 'freight' => 20.0, - 'weight' => 1.25, 'reconciliation_weight' => 1.25, 'reconciliation_volume' => 1.2, - 'month' => '2026-08', 'status' => '已发货', - ]], - 'total' => 1, - 'summary' => ['count' => 1, 'freight' => 20.0, 'payable_amount' => 20.0, 'actual_payment' => 0.0, 'confirmed_amount' => 0.0, 'reconciliation_difference' => 0.0], - 'state' => ['status' => 'succeeded'], - 'sync' => ['code' => 'fresh', 'last_succeeded_at' => '2026-08-02 09:00:00', 'lag_minutes' => 1], - ]); + $repository->shouldNotReceive('available', 'ready', 'paginate', 'requestSummary'); $this->app->instance(CourierFreightReconciliationMirrorRepository::class, $repository); $this->loginAs() ->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'freight-reconciliation', - 'express_no' => 'FIXTURE', ])) ->assertOk() - ->assertSeeText('FIXTURE-WAYBILL') + ->assertSeeText('原数据源已停用') ->assertSeeText('条形码') ->assertSeeText('交易订单号') ->assertSeeText('对账体积(cm3)') - ->assertSeeText('SKU-1') - ->assertSeeText('测试供应商') ->assertSeeText('运费合计') - ->assertSeeText('20.00') - ->assertSeeText('数据新鲜') - ->assertSeeText('共享业务镜像') + ->assertSee('data-summary-count>0', false) + ->assertDontSeeText('共享业务镜像') + ->assertDontSee('data-summary-url', false) ->assertDontSee('class="business-blank-page"', false); } -- 2.47.2