418 lines
21 KiB
PHP
418 lines
21 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Mockery;
|
|
use Modules\EcommerceFinance\Repositories\CourierSurchargeSettlementMirrorRepository;
|
|
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
|
|
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
|
|
use Tests\TestCase;
|
|
|
|
class CourierSurchargeSettlementMirrorRepositoryTest extends TestCase
|
|
{
|
|
private CourierSurchargeSettlementMirrorRepository $repository;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'database.connections.business_mirror' => [
|
|
'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '',
|
|
],
|
|
'upstream_mirrors.connection' => 'business_mirror',
|
|
]);
|
|
|
|
DB::purge('business_mirror');
|
|
|
|
$this->repository = new CourierSurchargeSettlementMirrorRepository(app('db'));
|
|
}
|
|
|
|
public function test_it_reports_unavailable_until_the_settlement_table_exists(): void
|
|
{
|
|
$this->assertFalse($this->repository->available());
|
|
$this->assertFalse($this->repository->ready());
|
|
$this->assertSame('pending', $this->repository->state()['code']);
|
|
}
|
|
|
|
public function test_it_filters_maps_and_summarises_settlement_rows(): void
|
|
{
|
|
$this->createTable();
|
|
|
|
DB::connection('business_mirror')->table(CourierSurchargeSettlementMirrorRepository::TABLE)->insert([
|
|
[
|
|
'rule_id' => 6, 'surcharge_type' => '超长费', 'courier_name' => '曹县德邦',
|
|
'waybill' => 'DPK1', 'outbound_io_id' => 1001, 'product_code' => 'DNZ032912N887V01',
|
|
'shop_name' => '拼多多-满富油家具旗舰店', 'online_order_no' => '261009-325069063762746',
|
|
'length' => 194, 'width' => 83, 'height' => 12.5, 'volume' => 201305,
|
|
'settlement_amount' => 8, 'settlement_status' => '已计算',
|
|
'shipped_at' => '2026-10-09 08:41:48', 'pulled_at' => '2026-10-09 12:00:00',
|
|
'evidence' => '最长边 194cm > 160cm',
|
|
'created_at' => '2026-10-09 12:00:00', 'updated_at' => '2026-10-09 12:00:00',
|
|
],
|
|
[
|
|
'rule_id' => 42, 'surcharge_type' => '超长费', 'courier_name' => '常州德邦',
|
|
'waybill' => 'DPK2', 'outbound_io_id' => 1002, 'product_code' => 'GBGJ-ZWJ024903A',
|
|
'shop_name' => '京东-HX旗舰店104', 'online_order_no' => null,
|
|
'length' => 244, 'width' => 7.5, 'height' => 8, 'volume' => 14640,
|
|
'settlement_amount' => 7, 'settlement_status' => '已计算',
|
|
'shipped_at' => '2026-10-08 10:00:00', 'pulled_at' => '2026-10-09 12:00:00',
|
|
'evidence' => '最长边 244cm > 160cm',
|
|
'created_at' => '2026-10-09 12:00:00', 'updated_at' => '2026-10-09 12:00:00',
|
|
],
|
|
]);
|
|
|
|
$this->assertTrue($this->repository->available());
|
|
$this->assertTrue($this->repository->ready());
|
|
|
|
$result = $this->repository->paginate([], 1, 25);
|
|
|
|
$this->assertSame(2, $result['total']);
|
|
$this->assertSame(15.0, $result['summary']['settlement_amount']);
|
|
$this->assertSame('曹县德邦', $result['data'][0]['courier_name']);
|
|
$this->assertSame('DPK1', $result['data'][0]['waybill']);
|
|
$this->assertSame('2026-10-09 08:41:48', $result['data'][0]['shipped_at']);
|
|
$this->assertSame('超长费', $result['data'][0]['surcharge_type']);
|
|
$this->assertSame(194.0, (float) $result['data'][0]['length']);
|
|
$this->assertSame(8.0, (float) $result['data'][0]['settlement_amount']);
|
|
$this->assertSame('fresh', $result['sync']['code']);
|
|
}
|
|
|
|
public function test_it_applies_page_filters(): void
|
|
{
|
|
$this->createTable();
|
|
|
|
DB::connection('business_mirror')->table(CourierSurchargeSettlementMirrorRepository::TABLE)->insert([
|
|
[
|
|
'rule_id' => 6, 'surcharge_type' => '超长费', 'courier_name' => '曹县德邦',
|
|
'waybill' => 'DPK1', 'outbound_io_id' => 1001, 'product_code' => 'DNZ032912N887V01',
|
|
'settlement_amount' => 8, 'settlement_status' => '已计算',
|
|
'shipped_at' => '2026-10-09 08:41:48', 'pulled_at' => '2026-10-09 12:00:00',
|
|
'created_at' => '2026-10-09 12:00:00', 'updated_at' => '2026-10-09 12:00:00',
|
|
],
|
|
[
|
|
'rule_id' => 42, 'surcharge_type' => '超长费', 'courier_name' => '常州德邦',
|
|
'waybill' => 'DPK2', 'outbound_io_id' => 1002, 'product_code' => 'GBGJ-ZWJ024903A',
|
|
'settlement_amount' => 7, 'settlement_status' => '待核价',
|
|
'shipped_at' => '2026-09-01 10:00:00', 'pulled_at' => '2026-10-09 12:00:00',
|
|
'created_at' => '2026-10-09 12:00:00', 'updated_at' => '2026-10-09 12:00:00',
|
|
],
|
|
]);
|
|
|
|
$byCourier = $this->repository->paginate(['courier_name' => '常州', 'status' => '待核价'], 1, 25);
|
|
$this->assertSame(1, $byCourier['total']);
|
|
$this->assertSame('常州德邦', $byCourier['data'][0]['courier_name']);
|
|
$this->assertSame(7.0, $byCourier['summary']['settlement_amount']);
|
|
|
|
$byDate = $this->repository->paginate(['created_from' => '2026-10-01', 'created_to' => '2026-10-09'], 1, 25);
|
|
$this->assertSame(1, $byDate['total']);
|
|
$this->assertSame('DPK1', $byDate['data'][0]['waybill']);
|
|
|
|
$byWaybill = $this->repository->paginate(['waybill' => 'DPK2'], 1, 25);
|
|
$this->assertSame(1, $byWaybill['total']);
|
|
$this->assertSame('常州德邦', $byWaybill['data'][0]['courier_name']);
|
|
}
|
|
|
|
public function test_import_decisions_report_settlement_and_reasons(): void
|
|
{
|
|
$this->createTable();
|
|
|
|
DB::connection('business_mirror')->table(CourierSurchargeSettlementMirrorRepository::TABLE)->insert([
|
|
[
|
|
'rule_id' => 6, 'surcharge_type' => '超长费', 'courier_name' => '曹县德邦',
|
|
'waybill' => 'DPK1', 'outbound_io_id' => 1001, 'product_code' => 'SKU-1',
|
|
'settlement_amount' => 8, 'settlement_status' => '已计算',
|
|
'shipped_at' => '2026-10-09 08:41:48', 'pulled_at' => '2026-10-09 12:00:00',
|
|
'evidence' => '最长边 194cm > 160cm',
|
|
'created_at' => '2026-10-09 12:00:00', 'updated_at' => '2026-10-09 12:00:00',
|
|
],
|
|
[
|
|
'rule_id' => 7, 'surcharge_type' => '退回费', 'courier_name' => '曹县德邦小件',
|
|
'waybill' => 'DPK2', 'outbound_io_id' => null, 'product_code' => 'SKU-2',
|
|
'settlement_amount' => 0, 'settlement_status' => '已计算',
|
|
'shipped_at' => '2026-09-01 10:00:00', 'pulled_at' => '2026-10-09 12:00:00',
|
|
'evidence' => '查件扣单/买家-不想要;售后退货退款无进仓/实退记录;结算金额 0.00',
|
|
'created_at' => '2026-10-09 12:00:00', 'updated_at' => '2026-10-09 12:00:00',
|
|
],
|
|
]);
|
|
|
|
$result = $this->repository->decideImportRows([
|
|
['row_number' => 2, 'waybill' => 'DPK1', 'surcharge_type' => '超长费'],
|
|
['row_number' => 3, 'waybill' => 'DPK2', 'surcharge_type' => '退回件'],
|
|
['row_number' => 4, 'waybill' => 'DPK3', 'surcharge_type' => '退回费'],
|
|
['row_number' => 5, 'waybill' => 'DPK4', 'surcharge_type' => '退回费', 'courier_name' => '恒思德邦'],
|
|
['row_number' => 6, 'waybill' => 'DPK1', 'surcharge_type' => '超长费', 'courier_name' => '常州德邦'],
|
|
]);
|
|
|
|
$this->assertSame('正常结', $result[0]['decision']);
|
|
$this->assertSame('', $result[0]['reason']);
|
|
$this->assertSame('曹县德邦', $result[0]['courier_name']);
|
|
$this->assertSame(8.0, (float) $result[0]['settlement_amount']);
|
|
|
|
$this->assertSame('不结', $result[1]['decision']);
|
|
$this->assertSame('退回费', $result[1]['surcharge_type']);
|
|
$this->assertStringContainsString('无进仓', $result[1]['reason']);
|
|
|
|
$this->assertSame('不结', $result[2]['decision']);
|
|
$this->assertNotSame('', $result[2]['reason']);
|
|
$this->assertNull($result[2]['settlement_amount']);
|
|
|
|
$this->assertSame('不结', $result[3]['decision']);
|
|
$this->assertStringContainsString('恒思德邦', $result[3]['reason']);
|
|
$this->assertSame('恒思德邦', $result[3]['courier_name']);
|
|
|
|
$this->assertSame('不结', $result[4]['decision']);
|
|
$this->assertStringContainsString('不一致', $result[4]['reason']);
|
|
}
|
|
|
|
public function test_import_derives_courier_and_amount_from_overlength_quote(): void
|
|
{
|
|
$this->createTable();
|
|
$this->seedSettlementPlaceholder();
|
|
$this->createSupportTables();
|
|
$this->seedQuotes([
|
|
['id' => 6, 'courier' => '曹县德邦', 'types' => ['超长费'], 'mode' => '按尺寸', 'condition' => '单边超160cm', 'standard' => '8元/单'],
|
|
]);
|
|
DB::connection('business_mirror')->table('ops_jst_sku')->insert([
|
|
['sku_id' => 'SKU-1', 'l' => 194, 'w' => 83, 'h' => 12.5, 'weight' => 2],
|
|
]);
|
|
$this->mockUpstream(static function (string $sql): array {
|
|
if (str_contains($sql, 'jst_salesOutboundInfo')) {
|
|
return [[
|
|
'Id' => 1, 'io_id' => 9001, 'o_id' => 7001, 'l_id' => 'DPK9', 'so_id' => 'SO-9',
|
|
'shop_name' => '拼多多-测试店', 'io_date' => '2026-10-10 10:00:00', 'logistics_company' => '曹县德邦',
|
|
]];
|
|
}
|
|
|
|
if (str_contains($sql, 'jst_salesOutboundItem')) {
|
|
return [['io_id' => 9001, 'sku_id' => 'SKU-1', 'i_id' => 'I-1', 'qty' => 1]];
|
|
}
|
|
|
|
if (str_contains($sql, 'Jst_SendDataDetail_View')) {
|
|
return [['io_id' => 9001, 'sku_id' => 'SKU-1', 'shop_i_id' => '993815011271']];
|
|
}
|
|
|
|
return [];
|
|
});
|
|
|
|
$result = $this->repository->decideImportRows([
|
|
['row_number' => 2, 'waybill' => 'DPK9', 'surcharge_type' => '超长费', 'courier_name' => ''],
|
|
]);
|
|
|
|
$this->assertSame('正常结', $result[0]['decision']);
|
|
$this->assertSame('曹县德邦', $result[0]['courier_name']);
|
|
$this->assertSame(8.0, (float) $result[0]['settlement_amount']);
|
|
$this->assertSame('已计算', $result[0]['settlement_status']);
|
|
$this->assertSame(194.0, (float) $result[0]['length']);
|
|
$this->assertSame('993815011271', $result[0]['platform_product_id']);
|
|
$this->assertSame('拼多多-测试店', $result[0]['shop_name']);
|
|
$this->assertStringContainsString('160', (string) $result[0]['evidence']);
|
|
}
|
|
|
|
public function test_import_derives_courier_and_amount_from_deduct_quotes(): void
|
|
{
|
|
$this->createTable();
|
|
$this->seedSettlementPlaceholder();
|
|
$this->createSupportTables();
|
|
$this->seedQuotes([
|
|
['id' => 20, 'courier' => '曹县德邦小件', 'types' => ['退回费'], 'mode' => '固定单价', 'condition' => '查件扣单并且买家原因已退回', 'standard' => '30元/单'],
|
|
['id' => 21, 'courier' => '曹县德邦小件', 'types' => ['拦截费'], 'mode' => '固定单价', 'condition' => '查件扣单', 'standard' => '20元/单'],
|
|
]);
|
|
DB::connection('business_mirror')->table('ops_jst_sku')->insert([
|
|
['sku_id' => 'SKU-2', 'l' => 0, 'w' => 0, 'h' => 0, 'weight' => 2.4],
|
|
]);
|
|
DB::connection('business_mirror')->table('js_jst_work_order_info')->insert([
|
|
'Id' => 1,
|
|
'title' => '查件',
|
|
'businessFieldList' => json_encode([
|
|
['fieldId' => 'lIdField', 'value' => 'DPK10'],
|
|
['fieldId' => 'logisticsField', 'value' => '曹县德邦小件'],
|
|
['fieldId' => 'o_id', 'value' => 7001],
|
|
['fieldId' => 'orderField', 'value' => '260710-1'],
|
|
['fieldId' => 'shopField', 'valueLabel' => '拼多多-测试店'],
|
|
['fieldId' => 'orderSendTime', 'value' => '2026-10-10 09:00:00'],
|
|
['fieldId' => 'SELECT_CASCADEField1413856620250813172526', 'value' => '扣单/买家-不想要'],
|
|
['fieldId' => 'itemInfo', 'childFieldList' => [[
|
|
['fieldId' => 'skuId', 'value' => 'SKU-2'],
|
|
['fieldId' => 'qty', 'value' => 1],
|
|
['fieldId' => 'platformItemId', 'value' => 'P-2'],
|
|
['fieldId' => 'iid', 'value' => 'I-2'],
|
|
]]],
|
|
], JSON_UNESCAPED_UNICODE),
|
|
]);
|
|
$this->mockUpstream(static function (string $sql): array {
|
|
if (str_contains($sql, 'jst_returnAndRefundInfo')) {
|
|
return [[
|
|
'o_id' => 7001, 'l_id' => 'DPK10', 'sku_id' => 'SKU-2', 'qty' => 1, 'r_qty' => 1,
|
|
'receive_date' => '2026-10-12 09:00:00',
|
|
]];
|
|
}
|
|
|
|
return [];
|
|
});
|
|
|
|
$result = $this->repository->decideImportRows([
|
|
['row_number' => 2, 'waybill' => 'DPK10', 'surcharge_type' => '退回费', 'courier_name' => ''],
|
|
['row_number' => 3, 'waybill' => 'DPK10', 'surcharge_type' => '拦截费', 'courier_name' => ''],
|
|
]);
|
|
|
|
$this->assertSame('正常结', $result[0]['decision']);
|
|
$this->assertSame('曹县德邦小件', $result[0]['courier_name']);
|
|
$this->assertSame(30.0, (float) $result[0]['settlement_amount']);
|
|
$this->assertSame(2.4, (float) $result[0]['settlement_weight']);
|
|
$this->assertSame('扣单', $result[0]['inspection_type']);
|
|
$this->assertSame('DPK10', $result[0]['return_waybill_no']);
|
|
|
|
$this->assertSame('正常结', $result[1]['decision']);
|
|
$this->assertSame(20.0, (float) $result[1]['settlement_amount']);
|
|
}
|
|
|
|
private function seedSettlementPlaceholder(): void
|
|
{
|
|
DB::connection('business_mirror')->table(CourierSurchargeSettlementMirrorRepository::TABLE)->insert([
|
|
'rule_id' => 1, 'surcharge_type' => '超长费', 'courier_name' => '占位快递',
|
|
'waybill' => 'PLACEHOLDER', 'settlement_amount' => 0, 'settlement_status' => '已计算',
|
|
'shipped_at' => '2026-10-01 00:00:00', 'pulled_at' => '2026-10-01 00:00:00',
|
|
'created_at' => '2026-10-01 00:00:00', 'updated_at' => '2026-10-01 00:00:00',
|
|
]);
|
|
}
|
|
|
|
private function createSupportTables(): void
|
|
{
|
|
$defaultSchema = Schema::connection((string) config('database.default'));
|
|
|
|
if (! $defaultSchema->hasTable('business_courier_surcharge_quotes')) {
|
|
$defaultSchema->create('business_courier_surcharge_quotes', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('courier');
|
|
$table->string('types');
|
|
$table->string('mode');
|
|
$table->string('condition');
|
|
$table->string('standard');
|
|
$table->string('status');
|
|
});
|
|
}
|
|
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('ops_jst_sku', function (Blueprint $table): void {
|
|
$table->string('sku_id');
|
|
$table->decimal('l', 12, 2)->default(0);
|
|
$table->decimal('w', 12, 2)->default(0);
|
|
$table->decimal('h', 12, 2)->default(0);
|
|
$table->decimal('weight', 12, 2)->default(0);
|
|
});
|
|
$schema->create('js_jst_work_order_info', function (Blueprint $table): void {
|
|
$table->bigIncrements('Id');
|
|
$table->string('title')->nullable();
|
|
$table->text('businessFieldList')->nullable();
|
|
});
|
|
}
|
|
|
|
/** @param list<array<string,mixed>> $rules */
|
|
private function seedQuotes(array $rules): void
|
|
{
|
|
$table = DB::connection((string) config('database.default'))->table('business_courier_surcharge_quotes');
|
|
$table->delete();
|
|
$table->insert(
|
|
array_map(static fn (array $rule): array => [
|
|
'id' => $rule['id'],
|
|
'company_id' => 1,
|
|
'courier' => $rule['courier'],
|
|
'scope' => '快递应付',
|
|
'reconciler' => '测试',
|
|
'types' => json_encode($rule['types'], JSON_UNESCAPED_UNICODE),
|
|
'mode' => $rule['mode'],
|
|
'condition' => $rule['condition'],
|
|
'standard' => $rule['standard'],
|
|
'effective_at' => '2026-01-01',
|
|
'status' => 'active',
|
|
], $rules),
|
|
);
|
|
}
|
|
|
|
/** @param callable(string,array):list<array<string,mixed>> $handler */
|
|
private function mockUpstream(callable $handler): void
|
|
{
|
|
$query = Mockery::mock(UpstreamReadOnlyQuery::class);
|
|
$query->shouldReceive('select')->andReturnUsing(
|
|
static fn (string $sql, array $bindings = []): array => $handler($sql, $bindings),
|
|
);
|
|
$factory = Mockery::mock(UpstreamReadOnlyQueryFactory::class);
|
|
$factory->shouldReceive('forSource')->andReturn($query);
|
|
$this->app->instance(UpstreamReadOnlyQueryFactory::class, $factory);
|
|
}
|
|
|
|
public function test_completed_waybill_index_uses_keyed_lookup_and_preserves_work_order_context(): void
|
|
{
|
|
$this->createTable();
|
|
DB::connection('business_mirror')->table(CourierSurchargeSettlementMirrorRepository::TABLE)->insert([
|
|
'rule_id' => 1, 'surcharge_type' => '退回费', 'courier_name' => '测试快递', 'waybill' => 'UNRELATED',
|
|
'settlement_amount' => 0, 'settlement_status' => '已计算', 'pulled_at' => now(), 'created_at' => now(), 'updated_at' => now(),
|
|
]);
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('js_jst_work_order_info', function (Blueprint $table): void {
|
|
$table->char('_mirror_key', 64)->primary();
|
|
$table->unsignedInteger('Id');
|
|
$table->string('title');
|
|
$table->text('businessFieldList');
|
|
});
|
|
config(['system_database.mode' => 'shared']);
|
|
(require base_path('database/migrations/2026_10_19_170000_create_work_order_waybill_lookup.php'))->up();
|
|
$fields = [
|
|
['fieldId' => 'lIdField', 'value' => '001234'],
|
|
['fieldId' => 'SELECT_CASCADEField1413856620250813172526', 'value' => '催件/未到'],
|
|
['fieldId' => 'soIdField', 'value' => 'SO-TEST'],
|
|
];
|
|
$db = DB::connection('business_mirror');
|
|
$db->table('js_jst_work_order_info')->insert([
|
|
'_mirror_key' => str_repeat('a', 64), 'Id' => 10, 'title' => '查件', 'businessFieldList' => json_encode($fields),
|
|
]);
|
|
app(\Modules\EcommerceFinance\Services\WorkOrderWaybillLookup::class)->backfill(500, 1, 0);
|
|
$db->enableQueryLog();
|
|
$results = $this->repository->decideImportRows([
|
|
['waybill' => '001234', 'surcharge_type' => '退回费'],
|
|
]);
|
|
$this->assertSame('催件', $results[0]['inspection_type']);
|
|
$this->assertStringContainsString('查件', $results[0]['reason']);
|
|
$queries = collect($db->getQueryLog())->pluck('query')->implode("\n");
|
|
$this->assertStringContainsString('work_order_waybill_lookup', $queries);
|
|
$this->assertStringNotContainsString('businessFieldList" like', $queries);
|
|
}
|
|
|
|
private function createTable(): void
|
|
{
|
|
Schema::connection('business_mirror')->create(
|
|
CourierSurchargeSettlementMirrorRepository::TABLE,
|
|
function (Blueprint $table): void {
|
|
$table->bigIncrements('id');
|
|
$table->unsignedInteger('rule_id');
|
|
$table->string('surcharge_type', 40);
|
|
$table->string('courier_name', 191);
|
|
$table->string('waybill', 191)->nullable();
|
|
$table->unsignedBigInteger('outbound_io_id')->nullable();
|
|
$table->string('platform_product_id', 191)->nullable();
|
|
$table->string('product_code', 191)->nullable();
|
|
$table->string('shop_name', 191)->nullable();
|
|
$table->string('online_order_no', 191)->nullable();
|
|
$table->decimal('length', 12, 2)->nullable();
|
|
$table->decimal('width', 12, 2)->nullable();
|
|
$table->decimal('height', 12, 2)->nullable();
|
|
$table->decimal('volume', 16, 2)->nullable();
|
|
$table->string('inspection_type', 60)->nullable();
|
|
$table->string('inspection_reason', 120)->nullable();
|
|
$table->string('return_waybill_no', 191)->nullable();
|
|
$table->decimal('settlement_weight', 12, 2)->nullable();
|
|
$table->decimal('settlement_amount', 12, 2)->default(0);
|
|
$table->string('settlement_status', 40)->default('已计算');
|
|
$table->string('evidence', 500)->nullable();
|
|
$table->dateTime('shipped_at')->nullable();
|
|
$table->dateTime('pulled_at');
|
|
$table->timestamps();
|
|
},
|
|
);
|
|
}
|
|
}
|