CaiWuXiTong/tests/Unit/AccessoryReshipPageDataTest.php

96 lines
7.0 KiB
PHP

<?php
namespace Tests\Unit;
use Illuminate\Container\Container;
use Illuminate\Http\Request;
use Modules\EcommerceFinance\Repositories\ShippingOrderRepository;
use Modules\EcommerceFinance\Services\AccessoryReshipMatcher;
use Modules\EcommerceFinance\View\AccessoryReshipPageData;
use Modules\EcommerceFinance\Services\AccessoryReshipCourierStore;
use Modules\EcommerceFinance\Services\AccessoryReshipRateCalculator;
use Modules\EcommerceFinance\Services\AccessoryReshipSelectionStore;
use PHPUnit\Framework\TestCase;
use Mockery;
require_once dirname(__DIR__, 2) . '/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php';
require_once dirname(__DIR__, 2) . '/modules/EcommerceFinance/Services/AccessoryReshipMatcher.php';
require_once dirname(__DIR__, 2) . '/modules/EcommerceFinance/View/AccessoryReshipPageData.php';
require_once dirname(__DIR__, 2) . '/modules/EcommerceFinance/Services/AccessoryReshipCourierStore.php';
require_once dirname(__DIR__, 2) . '/modules/EcommerceFinance/Services/AccessoryReshipRateCalculator.php';
require_once dirname(__DIR__, 2) . '/modules/EcommerceFinance/Services/AccessoryReshipSelectionStore.php';
class AccessoryReshipPageDataTest extends TestCase
{
private function selections(): AccessoryReshipSelectionStore
{
$store = Mockery::mock(AccessoryReshipSelectionStore::class);
$store->shouldReceive('forMonth')->andReturn([]);
return $store;
}
private function configs(): AccessoryReshipCourierStore
{
$store = Mockery::mock(AccessoryReshipCourierStore::class);
$store->shouldReceive('activeRates')->andReturn(['快递' => ['courier_code' => 'TEST', 'rate_basis_points' => 5000], '顺丰' => ['courier_code' => 'SF', 'rate_basis_points' => 10000]]);
return $store;
}
protected function tearDown(): void { Mockery::close(); parent::tearDown(); }
public function test_january_uses_current_month_and_previous_two_months(): void
{
$repo = Mockery::mock(ShippingOrderRepository::class);
$repo->shouldReceive('accessoryReshipBatches')->once()->with('2026-01-01', '2026-02-01', '2025-11-01')->andReturn((function () { yield ['accessories' => [['trade_no' => '001', 'express_no' => 'BF001', 'express_name1' => '补发快递', 'ship_time' => '2026-09-01', 'barcode' => 'SKU-1', 'freight' => '2.50']], 'originals' => []]; })());
$data = new AccessoryReshipPageData($repo, new AccessoryReshipMatcher, $this->configs(), new AccessoryReshipRateCalculator, $this->selections());
$rows = iterator_to_array($data->rows(['month' => '2026-01']));
$this->assertSame('unmatched', $rows[0]['status']);
$this->assertSame(0, $rows[0]['deduction_cents']);
}
public function test_summary_does_not_duplicate_candidate_freight_and_paginates(): void
{
$request = Request::create('/workspace/courier-payables/accessory-reship-deductions?month=2026-09&match_status=all&page=2');
Container::getInstance()->instance('request', $request);
$accessories = [];
for ($i = 0; $i < 105; $i++) $accessories[] = ['trade_no' => '001', 'express_no' => 'BF001', 'express_name1' => '补发快递', 'ship_time' => '2026-09-01', 'barcode' => 'SKU-1', 'freight' => '0.10'];
$original = ['trade_no' => '001', 'barcode' => 'SKU', 'express_no' => 'FIRST', 'express_name1' => '快递', 'ship_time' => '2026-08-01', 'warehouse_name' => '成品仓'];
$repo = Mockery::mock(ShippingOrderRepository::class);
$repo->shouldReceive('accessoryReshipBatches')->andReturn((function () use ($accessories, $original) { yield ['accessories' => $accessories, 'originals' => [$original, array_replace($original, ['express_no' => 'SECOND'])]]; })());
$data = (new AccessoryReshipPageData($repo, new AccessoryReshipMatcher, $this->configs(), new AccessoryReshipRateCalculator, $this->selections()))->page();
$this->assertCount(50, $data['reshipRows']);
$this->assertSame(105, $data['reshipRows']->total());
$this->assertSame(105, $data['reshipSummary']['matched']);
$this->assertSame(525, $data['reshipSummary']['amount']);
$this->assertSame(105, $data['reshipSummary']['ready']);
$this->assertSame(0, $data['reshipSummary']['ambiguous']);
}
public function test_filters_search_all_rows_and_keep_summary_pagination_consistent(): void
{
Container::getInstance()->instance('request', Request::create('/?month=2026-09&match_status=all&courier=补发&original_month=2026-08&page=2'));
$accessories = [];
for ($i = 0; $i < 105; $i++) $accessories[] = ['trade_no'=>'001', 'express_no'=>'BF'.$i, 'express_name1'=>'补发快递', 'ship_time'=>'2026-09-15', 'barcode'=>'SKU-1', 'freight'=>'0.10'];
$accessories[] = array_replace($accessories[0], ['trade_no'=>'NO-MATCH']);
$repo = Mockery::mock(ShippingOrderRepository::class);
$repo->shouldReceive('accessoryReshipBatches')->andReturn((function () use ($accessories) { yield ['accessories'=>$accessories, 'originals'=>[['trade_no'=>'001','barcode'=>'SKU','express_no'=>'SF001','express_name1'=>'顺丰','ship_time'=>'2026-08-01','warehouse_name'=>'成品仓']]]; })());
$data = (new AccessoryReshipPageData($repo, new AccessoryReshipMatcher, $this->configs(), new AccessoryReshipRateCalculator, $this->selections()))->page();
$this->assertNull($data['reshipError']);
$this->assertCount(50, $data['reshipRows']);
$this->assertSame(105, $data['reshipRows']->total());
$this->assertSame(105, $data['reshipSummary']['all']);
$this->assertSame(1050, $data['reshipSummary']['amount']);
}
public function test_filters_require_same_candidate_and_support_fields_and_unknown_amount(): void
{
$data = new AccessoryReshipPageData(Mockery::mock(ShippingOrderRepository::class), new AccessoryReshipMatcher, $this->configs(), new AccessoryReshipRateCalculator, $this->selections());
$row = ['trade_no'=>'T001','express_no'=>'BF001','express_name1'=>'','express_name2'=>'补发快递','ship_time'=>'2026-09-15','barcode'=>'SKU-1','normalized_barcode'=>'SKU','freight_cents'=>1230,'deduction_cents'=>1230,'matches'=>[['courier'=>'顺丰','express_no'=>'SF001','month'=>'2026-08'],['courier'=>'圆通','express_no'=>'YT001','month'=>'2026-09']]];
$this->assertTrue($data->matchesFilters($row, ['trade_no'=>'T001','express_no'=>'bf001','courier'=>'补发','ship_date'=>'2026-09-15','barcode'=>'SKU-1','normalized_barcode'=>'SKU','freight'=>'12.30','deduction_amount'=>'12.30','original_courier'=>'顺丰','original_express_no'=>'sf001','original_month'=>'2026-08']));
$this->assertFalse($data->matchesFilters($row, ['original_courier'=>'顺丰','original_express_no'=>'YT001']));
$this->assertFalse($data->matchesFilters($row, ['trade_no'=>'missing']));
$row['freight_cents'] = $row['deduction_cents'] = null;
$this->assertTrue($data->matchesFilters($row, ['freight'=>'待核实','deduction_amount'=>'待核实']));
$this->assertFalse($data->matchesFilters($row, ['deduction_amount'=>'0.00']));
}
}