CaiWuXiTong/tests/Unit/AccessoryReshipCourierConfigTest.php

168 lines
9.4 KiB
PHP
Raw Permalink 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;
use Illuminate\Config\Repository;
use Illuminate\Database\Capsule\Manager;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Translation\ArrayLoader;
use Illuminate\Translation\Translator;
use Illuminate\Validation\Factory;
use Illuminate\Validation\ValidationException;
use Modules\EcommerceFinance\Services\AccessoryReshipCourierStore;
use Modules\EcommerceFinance\Services\AccessoryReshipCourierImporter;
use Modules\EcommerceFinance\Services\AccessoryReshipRateCalculator;
use Modules\EcommerceFinance\Services\AccessoryReshipSelectionStore;
use PHPUnit\Framework\TestCase;
foreach (['AccessoryReshipCourierStore', 'AccessoryReshipCourierImporter', 'AccessoryReshipRateCalculator', 'AccessoryReshipSelectionStore'] as $class) {
require_once dirname(__DIR__, 2) . '/modules/EcommerceFinance/Services/' . $class . '.php';
}
class AccessoryReshipCourierConfigTest extends TestCase
{
private AccessoryReshipCourierStore $store;
protected function setUp(): void
{
$app = new Application(dirname(__DIR__, 2));
$app->instance('config', new Repository(['app' => ['platform_timezone' => 'Asia/Shanghai']]));
$db = new Manager($app);
$db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '']);
$app->instance('db', $db->getDatabaseManager());
$app->bind('db.schema', fn () => $db->getConnection()->getSchemaBuilder());
$app->instance('validator', new Factory(new Translator(new ArrayLoader, 'zh_CN'), $app));
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
$migration = require dirname(__DIR__, 2) . '/database/migrations/2026_10_16_220000_create_accessory_reship_courier_configs.php';
$migration->up();
$this->store = new AccessoryReshipCourierStore;
}
private function row(string $code = 'TEST', string $name = '测试快递', int $rate = 5000): array
{
return ['courier_code' => $code, 'courier_name' => $name, 'rate_basis_points' => $rate, 'source_created_at' => '2025-11-07 17:03:30', 'creator' => '原创建人'];
}
public function test_import_preserves_source_metadata_and_is_idempotent(): void
{
$this->assertSame(['added' => 1, 'skipped' => 0], $this->store->import(1, [$this->row()], '导入操作人', 'hash'));
$this->assertSame(['added' => 0, 'skipped' => 1], $this->store->import(1, [$this->row()], '导入操作人', 'hash'));
$row = DB::table(AccessoryReshipCourierStore::TABLE)->first();
$this->assertSame('2025-11-07 17:03:30', $row->source_created_at);
$this->assertSame('原创建人', $row->creator);
$this->assertSame(1, DB::table(AccessoryReshipCourierStore::TABLE)->count());
$this->assertSame(2, DB::table(AccessoryReshipCourierStore::EVENTS)->count());
}
public function test_import_conflict_rolls_back_entire_batch_and_log(): void
{
$this->store->import(1, [$this->row()], '操作人', 'hash');
try {
$this->store->import(1, [$this->row('NEW', '新增快递'), $this->row('TEST', '不同名称')], '操作人', 'hash2');
$this->fail('必须拒绝冲突');
} catch (ValidationException) {
$this->assertSame(1, DB::table(AccessoryReshipCourierStore::TABLE)->count());
$this->assertSame(1, DB::table(AccessoryReshipCourierStore::EVENTS)->count());
}
}
public function test_crud_soft_delete_restore_and_company_isolation(): void
{
$id = $this->store->save(1, $this->row(), '操作人');
$this->store->save(1, $this->row(rate: 7000), '编辑人', $id);
$this->assertSame(7000, (int) $this->store->activeRates(1)['测试快递']['rate_basis_points']);
$this->assertSame([], $this->store->activeRates(2));
$this->store->changeDeleted(1, $id, true, '操作人');
$this->assertSame([], $this->store->activeRates(1));
$this->store->changeDeleted(1, $id, false, '操作人');
$this->assertCount(1, $this->store->activeRates(1));
$this->assertSame(4, DB::table(AccessoryReshipCourierStore::EVENTS)->count());
try {
$this->store->save(2, $this->row(), '其他公司', $id);
$this->fail('不能修改其他公司');
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $exception) {
$this->assertSame(404, $exception->getStatusCode());
}
}
public function test_rate_calculation_zero_missing_conflicting_and_rounding(): void
{
$calculator = new AccessoryReshipRateCalculator;
$row = ['status' => 'matched', 'freight_cents' => 1235, 'matches' => [['courier' => '测试快递'], ['courier' => '另一快递']]];
$rates = ['测试快递' => $this->row(rate: 5000), '另一快递' => $this->row('SECOND', '另一快递', 5000)];
$this->assertSame('selection_required', $calculator->apply($row, $rates)['deduction_state']);
$result = $calculator->apply($row, $rates, '测试快递');
$this->assertSame(618, $result['deduction_cents']);
$this->assertSame('ready', $result['deduction_state']);
$rates['另一快递']['rate_basis_points'] = 10000;
$this->assertSame('selection_required', $calculator->apply($row, $rates)['deduction_state']);
$this->assertNull($calculator->apply($row, $rates)['deduction_cents']);
$this->assertSame('missing_config', $calculator->apply($row, [], '测试快递')['deduction_state']);
$this->assertSame(1235, $calculator->apply($row, $rates, '另一快递')['deduction_cents']);
$this->assertSame('selection_required', $calculator->apply($row, $rates, '失效快递')['deduction_state']);
$row['matches'] = [['courier' => '测试快递']];
$rates['测试快递']['rate_basis_points'] = 0;
$this->assertSame(0, $calculator->apply($row, $rates)['deduction_cents']);
$this->assertSame('zero_rate', $calculator->apply($row, $rates)['deduction_state']);
$this->assertSame(865, AccessoryReshipRateCalculator::amount(1235, 7000));
$row['status'] = 'unmatched';
$this->assertSame(0, $calculator->apply($row, $rates)['deduction_cents']);
}
public function test_same_courier_selects_one_waybill_without_confirmation(): void
{
$row = ['status' => 'matched', 'freight_cents' => 1235, 'matches' => [
['courier' => '测试快递', 'express_no' => 'FIRST', 'month' => '2026-09'],
['courier' => '测试快递', 'express_no' => 'SECOND', 'month' => '2026-08'],
]];
$result = (new AccessoryReshipRateCalculator)->apply($row, ['测试快递' => $this->row()]);
$this->assertSame('ready', $result['deduction_state']);
$this->assertSame(618, $result['deduction_cents']);
$this->assertCount(1, $result['matches']);
$this->assertSame('FIRST', $result['matches'][0]['express_no']);
$this->assertStringNotContainsString('核对', $result['deduction_note']);
}
public function test_selection_survives_reload_and_is_scoped_to_company_month_and_package(): void
{
$migration = require dirname(__DIR__, 2) . '/database/migrations/2026_10_16_230000_create_accessory_reship_selections.php';
$migration->up();
$store = new AccessoryReshipSelectionStore;
$key = AccessoryReshipSelectionStore::key(['source_id' => 1, 'express_no' => 'BF001']);
$other = AccessoryReshipSelectionStore::key(['source_id' => 2, 'express_no' => 'BF002']);
$this->assertNotSame($key, $other);
$store->save(1, '2026-10', $key, '快递A', '操作人');
$store->save(1, '2026-10', $key, '快递B', '操作人');
$store->save(1, '2026-10', $other, '快递C', '操作人');
$this->assertSame([$key => '快递B', $other => '快递C'], (new AccessoryReshipSelectionStore)->forMonth(1, '2026-10'));
$this->assertSame([], $store->forMonth(2, '2026-10'));
$this->assertSame([], $store->forMonth(1, '2026-09'));
$this->assertSame(2, DB::table(AccessoryReshipSelectionStore::TABLE)->count());
}
public function test_importer_reads_string_and_numeric_percent_and_retains_dates(): void
{
$book = new \PhpOffice\PhpSpreadsheet\Spreadsheet;
$sheet = $book->getActiveSheet();
$sheet->fromArray([['快递代码', '快递公司', '创建时间', '创建人', '扣款比率(%)'], ['A', '快递A', '2025-11-07 17:03:30', '原创建人', '50.00%'], ['B', '快递B', '2025-11-08 12:00:00', '原创建人', 0.7]]);
$sheet->getStyle('E3')->getNumberFormat()->setFormatCode('0.00%');
$path = tempnam(sys_get_temp_dir(), 'reship-config-');
try {
(new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($book))->save($path);
$rows = (new AccessoryReshipCourierImporter)->read($path);
$this->assertSame([5000, 7000], array_column($rows, 'rate_basis_points'));
$this->assertSame('2025-11-07 17:03:30', $rows[0]['source_created_at']);
$sheet->setCellValue('E3', '100.01%');
(new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($book))->save($path);
$this->expectException(ValidationException::class);
(new AccessoryReshipCourierImporter)->read($path);
} finally {
$book->disconnectWorksheets();
unlink($path);
}
}
}