369 lines
18 KiB
PHP
369 lines
18 KiB
PHP
<?php
|
||
|
||
namespace Tests\Feature\Business;
|
||
|
||
use App\Models\Auth\Permission;
|
||
use Illuminate\Http\UploadedFile;
|
||
use Mockery;
|
||
use Modules\EcommerceFinance\Repositories\CourierSurchargeSettlementMirrorRepository;
|
||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||
use Tests\Feature\FeatureTestCase;
|
||
|
||
class CourierSurchargeSettlementPageTest extends FeatureTestCase
|
||
{
|
||
public function test_page_renders_requested_settlement_columns_and_mirror_state(): 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',
|
||
]))->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);
|
||
}
|
||
}
|
||
}
|