user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'courier-surcharge-settlement', ]))->assertOk() ->assertSeeText('附加费结算') ->assertSeeText('共享业务镜像尚未建立,附加费结算暂时无法读取。') ->assertSee('data-finance-table-name="courier-surcharge-settlement"', false) ->assertSee('data-finance-fixed="true"', false) ->assertSee('data-finance-fixed="end"', false) ->assertSeeText('快递名称') ->assertSeeText('出库时间') ->assertSeeText('平台商品ID') ->assertSeeText('查件原因') ->assertSeeText('退回单号') ->assertSeeText('结算重量') ->assertSeeText('结算金额') ->assertSeeText('附加费类型'); } public function test_settlement_filters_are_preserved_in_the_form(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'courier-surcharge-settlement', 'courier_name' => '测试快递', 'waybill' => '001234', 'shop_name' => '测试店铺', 'online_order_no' => 'SO-001', 'status' => '已计算', ]))->assertOk() ->assertSee('value="测试快递"', false) ->assertSee('value="001234"', false) ->assertSee('value="测试店铺"', false) ->assertSee('value="SO-001"', false) ->assertSee('value="已计算" selected', false) ->assertSee('data-settlement-filter-picker', false) ->assertSee('data-settlement-filter-check', false) ->assertSee('data-settlement-filter-field="waybill"', false) ->assertSeeText('筛选设置') ->assertViewHas('content', fn (array $content) => $content['courierSurchargeSettlementFilters']['status'] === '已计算'); } public function test_quote_page_only_exposes_quote_tab(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'courier-surcharge', ]))->assertOk() ->assertSeeText('附加费报价') ->assertDontSee(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'courier-surcharge', 'tab' => 'settlement', ]), false) ->assertDontSee('data-surcharge-panel="settlement"', false) ->assertSee('data-surcharge-panel="quotes"', false); } public function test_old_settlement_tab_url_redirects_to_standalone_page(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'courier-surcharge', 'tab' => 'settlement', 'waybill' => '0000123456789', ]))->assertRedirect(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'courier-surcharge-settlement', 'waybill' => '0000123456789', ])); } public function test_settlement_page_renders_as_courier_payables_page(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'courier-surcharge-settlement', 'waybill' => '0000123456789', ]))->assertOk() ->assertSeeText('附加费结算') ->assertSee('data-finance-table-name="courier-surcharge-settlement"', false) ->assertSee('value="0000123456789"', false); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', ]))->assertOk() ->assertSeeText('附加费结算'); } public function test_settlement_rows_support_click_highlight(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = Mockery::mock(CourierSurchargeSettlementMirrorRepository::class); $repository->shouldReceive('available')->andReturn(true); $repository->shouldReceive('ready')->andReturn(true); $repository->shouldReceive('paginate')->andReturn([ 'data' => [[ 'courier_name' => '曹县德邦', 'shipped_at' => '2026-10-12 18:38:33', 'waybill' => 'DPK317248316615', 'platform_product_id' => '993815011271', 'product_code' => 'MKHDNZ0409260601380', 'shop_name' => '拼多多-HX旗舰店101', 'online_order_no' => '260912-325069063762746', 'length' => 168, 'width' => 30, 'height' => 20, 'volume' => 100800, 'inspection_type' => null, 'inspection_reason' => null, 'return_waybill_no' => null, 'settlement_weight' => null, 'settlement_amount' => 8, 'surcharge_type' => '超长费', ]], 'total' => 30, 'has_more' => false, 'summary' => ['count' => 30, 'settlement_amount' => 8.0], 'sync' => ['code' => 'fresh', 'last_succeeded_at' => '2026-10-13 06:31:34'], 'state' => ['code' => 'fresh', 'last_succeeded_at' => '2026-10-13 06:31:34'], ]); $this->app->instance(CourierSurchargeSettlementMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'courier-surcharge-settlement', ]))->assertOk() ->assertSee('data-settlement-row', false) ->assertSee('is-clickable', false) ->assertSee('is-active', false) ->assertSee('settlement-page-link', false) ->assertDontSee('page-item', false) ->assertSeeText('第 1 / 2 页,本页 1 条,共 30 条') ->assertSeeText('点击行可高亮标记') ->assertSeeText('曹县德邦') ->assertSeeText('超长费'); } public function test_settlement_export_streams_filtered_rows_as_xlsx(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = Mockery::mock(CourierSurchargeSettlementMirrorRepository::class); $repository->shouldReceive('exportRows') ->once() ->withArgs(static fn (array $filters): bool => ($filters['courier_name'] ?? '') === '曹县德邦') ->andReturn((function (): \Generator { yield (object) [ 'courier_name' => '曹县德邦', 'waybill' => 'DPK317249507921', 'shipped_at' => '2026-10-09 08:39:40', 'platform_product_id' => null, 'product_code' => 'DNZ032912N887V01', 'shop_name' => '拼多多-满富油家具旗舰店', 'online_order_no' => '261009-325069063762746', 'length' => 194, 'width' => 83, 'height' => 12.5, 'volume' => 201305, 'inspection_type' => null, 'inspection_reason' => null, 'return_waybill_no' => null, 'settlement_weight' => null, 'settlement_amount' => 8, 'surcharge_type' => '超长费', ]; })()); $this->app->instance(CourierSurchargeSettlementMirrorRepository::class, $repository); $response = $this->loginAs()->get(route('business.courier-surcharge.settlement.export', [ 'courier_name' => '曹县德邦', ])); $response->assertOk(); $this->assertStringContainsString( 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', (string) $response->headers->get('content-type'), ); $path = tempnam(sys_get_temp_dir(), 'surcharge_settlement_export_'); file_put_contents($path, $response->streamedContent()); $sheet = IOFactory::load($path)->getActiveSheet(); $plain = static fn (mixed $value): mixed => $value instanceof RichText ? $value->getPlainText() : $value; $headers = []; foreach ($sheet->getRowIterator(1, 1) as $row) { foreach ($row->getCellIterator() as $cell) { $headers[$cell->getColumn()] = $plain($cell->getValue()); } } $column = static fn (string $label): string => (string) array_search($label, $headers, true); $value = static fn (string $label): mixed => $plain($sheet->getCell($column($label) . '2')->getValue()); try { $this->assertSame('快递名称', $headers['A']); $this->assertSame('出库时间', $headers['B']); $this->assertSame('单号', $headers['C']); $this->assertSame('附加费类型', $headers['Q']); $this->assertSame('曹县德邦', $value('快递名称')); $this->assertSame('DPK317249507921', $value('单号')); $this->assertSame('2026-10-09 08:39:40', $value('出库时间')); $this->assertSame('DNZ032912N887V01', $value('商品编码')); $this->assertSame('拼多多-满富油家具旗舰店', $value('店铺名称')); $this->assertEquals(194, $value('长')); $this->assertEquals(12.5, $value('高')); $this->assertEquals(201305, $value('体积')); $this->assertEquals(8, $value('结算金额')); $this->assertSame('超长费', $value('附加费类型')); $this->assertNull($value('查件类型')); } finally { unlink($path); } } public function test_settlement_import_streams_decision_workbook(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = Mockery::mock(CourierSurchargeSettlementMirrorRepository::class); $repository->shouldReceive('decideImportRows') ->once() ->withArgs(static function (array $pairs): bool { return count($pairs) === 2 && $pairs[0]['waybill'] === 'DPK317138931411' && $pairs[0]['surcharge_type'] === '退回费' && $pairs[0]['courier_name'] === '' && $pairs[0]['amount'] === 10.0 && $pairs[1]['waybill'] === 'DPK1' && $pairs[1]['surcharge_type'] === '超长费' && $pairs[1]['courier_name'] === '' && $pairs[1]['amount'] === null; }) ->andReturn([ [ 'row_number' => 2, 'waybill' => 'DPK317138931411', 'surcharge_type' => '退回费', 'decision' => '正常结', 'reason' => '', 'courier_name' => '曹县德邦小件', 'shipped_at' => '2026-07-09 13:25:51', 'platform_product_id' => '968882879362', 'product_code' => 'YMJ000904N722', 'shop_name' => '拼多多-卓禾住宅家具旗舰店', 'online_order_no' => '260709-594595049040320', 'length' => null, 'width' => null, 'height' => null, 'volume' => null, 'inspection_type' => '扣单', 'inspection_reason' => '买家-不想要', 'return_waybill_no' => 'DPK317138931411', 'settlement_weight' => 2.4, 'settlement_amount' => 20, 'settlement_status' => '已计算', 'evidence' => '查件扣单/买家-不想要;退款确认 2026-07-15 15:00:09;售后退货退款已进仓 2026-07-18 09:53:32;实退 1', ], [ 'row_number' => 3, 'waybill' => 'DPK1', 'surcharge_type' => '超长费', 'decision' => '不结', 'reason' => '未找到超长费结算行:单边未超报价阈值或不在已拉取范围', 'courier_name' => null, 'shipped_at' => null, 'platform_product_id' => null, 'product_code' => null, 'shop_name' => null, 'online_order_no' => null, 'length' => null, 'width' => null, 'height' => null, 'volume' => null, 'inspection_type' => null, 'inspection_reason' => null, 'return_waybill_no' => null, 'settlement_weight' => null, 'settlement_amount' => null, 'settlement_status' => null, 'evidence' => null, ], ]); $this->app->instance(CourierSurchargeSettlementMirrorRepository::class, $repository); $spreadsheet = new Spreadsheet(); $spreadsheet->getActiveSheet()->fromArray([ ['单号', '附加费类型', '金额'], ['DPK317138931411', '退回费', 10], ['DPK1', '超长费'], ]); $importPath = tempnam(sys_get_temp_dir(), 'surcharge_import_'); (new Xlsx($spreadsheet))->save($importPath); $response = $this->loginAs()->post(route('business.courier-surcharge.settlement.import'), [ 'file' => new UploadedFile( $importPath, 'surcharge-import.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', null, true, ), ]); $response->assertOk(); $this->assertStringContainsString( 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', (string) $response->headers->get('content-type'), ); $resultPath = tempnam(sys_get_temp_dir(), 'surcharge_import_result_'); file_put_contents($resultPath, $response->streamedContent()); $sheet = IOFactory::load($resultPath)->getActiveSheet(); $plain = static fn (mixed $value): mixed => $value instanceof RichText ? $value->getPlainText() : $value; try { $this->assertSame('序号', $plain($sheet->getCell('A1')->getValue())); $this->assertSame('结算结论', $plain($sheet->getCell('D1')->getValue())); $this->assertSame('不结原因', $plain($sheet->getCell('E1')->getValue())); $this->assertSame('正常结', $plain($sheet->getCell('D2')->getValue())); $this->assertSame('DPK317138931411', $plain($sheet->getCell('B2')->getValue())); $this->assertSame('退回费', $plain($sheet->getCell('C2')->getValue())); $this->assertSame('导入金额', $plain($sheet->getCell('U1')->getValue())); $this->assertSame('差异金额', $plain($sheet->getCell('V1')->getValue())); $this->assertEquals(10, $plain($sheet->getCell('U2')->getValue())); $this->assertEquals(10, $plain($sheet->getCell('V2')->getValue())); $this->assertSame('不结', $plain($sheet->getCell('D3')->getValue())); $this->assertStringContainsString('未找到超长费', (string) $plain($sheet->getCell('E3')->getValue())); $this->assertNull($plain($sheet->getCell('V3')->getValue())); } finally { @unlink($importPath); @unlink($resultPath); } } public function test_settlement_import_template_streams_workbook(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $response = $this->loginAs()->get(route('business.courier-surcharge.settlement.import-template')); $response->assertOk(); $this->assertStringContainsString( 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', (string) $response->headers->get('content-type'), ); $path = tempnam(sys_get_temp_dir(), 'surcharge_import_template_'); file_put_contents($path, $response->streamedContent()); $sheet = IOFactory::load($path)->getActiveSheet(); $plain = static fn (mixed $value): mixed => $value instanceof RichText ? $value->getPlainText() : $value; try { $this->assertSame('单号', $plain($sheet->getCell('A1')->getValue())); $this->assertSame('附加费类型', $plain($sheet->getCell('B1')->getValue())); $this->assertSame('金额(可选)', $plain($sheet->getCell('C1')->getValue())); $this->assertNull($plain($sheet->getCell('D1')->getValue())); $this->assertSame('DPK317138931411', $plain($sheet->getCell('A2')->getValue())); $this->assertSame('退回费', $plain($sheet->getCell('B2')->getValue())); $this->assertEquals(20, $plain($sheet->getCell('C2')->getValue())); } finally { @unlink($path); } } }