CaiWuXiTong/tests/Unit/Business/ReverseLogisticsQuoteSnapshotTest.php

156 lines
7.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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']);
}
}