156 lines
7.7 KiB
PHP
156 lines
7.7 KiB
PHP
<?php
|
||
|
||
namespace Tests\Unit\Business;
|
||
|
||
use App\Support\ReverseLogisticsQuoteSnapshot;
|
||
use Tests\TestCase;
|
||
|
||
/** 逆向物流报价拆分:矩阵、单一到达省、重量段三种版式都要还原成可计算运价。 */
|
||
final class ReverseLogisticsQuoteSnapshotTest extends TestCase
|
||
{
|
||
private string $fixturePath;
|
||
|
||
protected function setUp(): void
|
||
{
|
||
parent::setUp();
|
||
|
||
$this->fixturePath = storage_path('framework/testing/reverse-logistics-' . uniqid('', true) . '.json');
|
||
file_put_contents($this->fixturePath, json_encode([
|
||
'sourcePath' => 'C:/private/逆向物流报价.xlsx',
|
||
'generatedAt' => '2026-09-29 12:00:00',
|
||
'sheets' => [
|
||
[
|
||
'index' => 0,
|
||
'name' => '申通退货',
|
||
'address' => 'A1:F8',
|
||
'values' => [
|
||
['报价单', null, null, null, null, null],
|
||
['出发省份', '到达省份', '江苏', null, '安徽', null],
|
||
[null, null, '面单价格', '续重价格', '面单价格', '续重价格'],
|
||
[null, '江苏', 9, 1, 9, 1],
|
||
[null, '广东', 9, 1.75, 9, 1.75],
|
||
['1、该价格不含税,生效日期:2026年5月1日', null, null, null, null, null],
|
||
['4、计泡标准:长*宽*高/6000', null, null, null, null, null],
|
||
['7、结算方式:总价=首重+续重*(货物结算重量-1)', null, null, null, null, null],
|
||
],
|
||
'formulas' => [[], [], [], [], []],
|
||
],
|
||
[
|
||
'index' => 1,
|
||
'name' => '常州德邦逆向',
|
||
'address' => 'A1:D5',
|
||
'values' => [
|
||
['常州德邦逆向报价2026.4.7', null, null, null],
|
||
['出发省份', '到达省份江苏', null, '备注:'],
|
||
[null, '面单价格', '续重价格', null],
|
||
['江苏', 9, 1, '1、此价格单件限重60KG以内 单边超1.6米或三边超2.5米加收7元每单'],
|
||
['新疆', 18, 5.5, '6.计算方式:面单+重量*费率'],
|
||
],
|
||
'formulas' => [[], [], [], [], []],
|
||
],
|
||
[
|
||
'index' => 2,
|
||
'name' => '中通快运逆向',
|
||
'address' => 'A1:F4',
|
||
'values' => [
|
||
['中通快运逆向2026.4.21起', null, null, null, null, null],
|
||
['省份', '面单费', '省份', '0-30kg', '31-60kg', '备注'],
|
||
['安徽省', 9, '安徽省', 0.95, 0.95, '1、目前该报价仅支持常州仓收件地址。'],
|
||
['新疆', '/', '新疆', '/', '/', null],
|
||
],
|
||
'formulas' => [[], [], [], []],
|
||
],
|
||
[
|
||
'index' => 3,
|
||
'name' => '未识别报价表',
|
||
'address' => 'A1:B2',
|
||
'values' => [['随便', '什么'], ['没有报价结构', null]],
|
||
'formulas' => [[], []],
|
||
],
|
||
],
|
||
], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
|
||
}
|
||
|
||
protected function tearDown(): void
|
||
{
|
||
if (isset($this->fixturePath) && is_file($this->fixturePath)) {
|
||
unlink($this->fixturePath);
|
||
}
|
||
|
||
parent::tearDown();
|
||
}
|
||
|
||
public function testItSplitsEverySupportedSheetLayoutIntoRates(): void
|
||
{
|
||
$snapshot = (new ReverseLogisticsQuoteSnapshot($this->fixturePath))->load();
|
||
|
||
$this->assertTrue($snapshot['available']);
|
||
$this->assertSame(['未识别报价表'], $snapshot['unparsed_sheets']);
|
||
$this->assertSame(['申通', '德邦', '中通快运'], array_column($snapshot['rate_groups'], 'carrier'));
|
||
$this->assertSame(10, $snapshot['rate_summary']['rate_count']);
|
||
$this->assertSame(8, $snapshot['rate_summary']['available_count']);
|
||
$this->assertSame(2, $snapshot['rate_summary']['missing_count']);
|
||
$this->assertSame(2, $snapshot['rate_summary']['weight_band_count']);
|
||
$this->assertSame(['江苏', '广东', '新疆', '安徽'], $snapshot['provinces']['origins']);
|
||
$this->assertSame(['江苏', '安徽'], $snapshot['provinces']['destinations']);
|
||
|
||
$rules = collect($snapshot['rate_groups'])->keyBy('carrier');
|
||
|
||
$this->assertSame(4, $rules['申通']['rate_count']);
|
||
$this->assertSame('2026-05-01', $rules['申通']['effective_date']);
|
||
$this->assertSame('首重 + 续重 × (结算重量 - 1)', $rules['申通']['pricing_label']);
|
||
$this->assertSame('行=出发省份,列=到达省份(2 个到达省)', $rules['申通']['route_label']);
|
||
$this->assertContains('4、计泡标准:长*宽*高/6000', $rules['申通']['constraints']);
|
||
|
||
$this->assertSame(2, $rules['德邦']['rate_count']);
|
||
$this->assertSame('2026-04-07', $rules['德邦']['effective_date']);
|
||
$this->assertSame('面单 + 重量 × 费率', $rules['德邦']['pricing_label']);
|
||
|
||
$this->assertSame(4, $rules['中通快运']['rate_count']);
|
||
$this->assertSame(2, $rules['中通快运']['available_count']);
|
||
$this->assertSame('2026-04-21', $rules['中通快运']['effective_date']);
|
||
$this->assertSame(['0-30kg', '31-60kg'], $rules['中通快运']['weight_bands']);
|
||
$this->assertSame(1, $rules['中通快运']['destination_count']);
|
||
|
||
$rates = collect($snapshot['rates']);
|
||
|
||
$shentong = $rates->firstWhere(fn (array $rate) => $rate['carrier'] === '申通'
|
||
&& $rate['origin'] === '广东'
|
||
&& $rate['destination'] === '江苏');
|
||
$this->assertNotNull($shentong);
|
||
$this->assertSame(9.0, $shentong['base_amount']);
|
||
$this->assertSame(1.75, $shentong['increment_amount']);
|
||
$this->assertSame('C5:D5', $shentong['source']);
|
||
$this->assertSame('广东', $shentong['origin_key']);
|
||
$this->assertSame('江苏', $shentong['destination_key']);
|
||
$this->assertTrue($shentong['available']);
|
||
|
||
$deppon = $rates->firstWhere(fn (array $rate) => $rate['carrier'] === '德邦' && $rate['origin'] === '新疆');
|
||
$this->assertNotNull($deppon);
|
||
$this->assertSame('江苏', $deppon['destination']);
|
||
$this->assertSame(18.0, $deppon['base_amount']);
|
||
$this->assertSame(5.5, $deppon['increment_amount']);
|
||
|
||
$bandRates = $rates->filter(fn (array $rate) => $rate['carrier'] === '中通快运' && $rate['origin'] === '安徽省');
|
||
$this->assertCount(2, $bandRates);
|
||
$this->assertSame(['0-30kg', '31-60kg'], array_column($bandRates->values()->all(), 'weight_band'));
|
||
$this->assertSame('安徽', $bandRates->first()['origin_key']);
|
||
$this->assertSame('江苏', $bandRates->first()['destination_key']);
|
||
|
||
$missing = $rates->filter(fn (array $rate) => ! $rate['available']);
|
||
$this->assertCount(2, $missing);
|
||
$this->assertSame(['新疆', '新疆'], $missing->pluck('origin')->values()->all());
|
||
}
|
||
|
||
public function testItKeepsTheShellWhenTheSnapshotIsMissing(): void
|
||
{
|
||
$snapshot = (new ReverseLogisticsQuoteSnapshot(storage_path('framework/testing/missing-reverse-logistics.json')))->load();
|
||
|
||
$this->assertFalse($snapshot['available']);
|
||
$this->assertSame('missing', $snapshot['state']);
|
||
$this->assertSame('尚未导入本机逆向物流报价文件。', $snapshot['message']);
|
||
$this->assertSame([], $snapshot['rates']);
|
||
$this->assertSame([], $snapshot['rate_groups']);
|
||
}
|
||
}
|