239 lines
12 KiB
PHP
239 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\NonJingxiRefundMirrorRepository;
|
|
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
|
|
use Tests\TestCase;
|
|
|
|
class NonJingxiRefundMirrorRepositoryTest extends TestCase
|
|
{
|
|
private NonJingxiRefundMirrorRepository $repository;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'database.connections.business_mirror' => [
|
|
'driver' => 'sqlite',
|
|
'database' => ':memory:',
|
|
'prefix' => '',
|
|
],
|
|
'upstream_mirrors.connection' => 'business_mirror',
|
|
'upstream_mirrors.datasets' => $this->definitions(),
|
|
]);
|
|
DB::purge('business_mirror');
|
|
$this->createTables();
|
|
$this->markReady();
|
|
|
|
$generic = new GenericBusinessMirrorRepository(app('db'));
|
|
$this->repository = new NonJingxiRefundMirrorRepository(
|
|
new BusinessMirrorDatasetRegistry(),
|
|
$generic,
|
|
);
|
|
}
|
|
|
|
public function test_ready_mirror_supports_second_page_filters_and_current_page_enrichment(): void
|
|
{
|
|
$db = DB::connection('business_mirror');
|
|
$db->table('ops_jst_return_refund_info')->insert([
|
|
$this->refundInfo(1, 101, '2026-08-19 10:00:00', '天猫', '一号店'),
|
|
$this->refundInfo(2, 102, '2026-08-20 10:00:00', '抖音', '二号店'),
|
|
$this->refundInfo(3, 103, '2026-08-21 10:00:00', 'jingxi', '京喜店'),
|
|
]);
|
|
$db->table('ops_jst_return_refund_item')->insert([
|
|
$this->refundItem(11, 101, 'SKU-1', 'SUB-1'),
|
|
$this->refundItem(12, 102, 'SKU-2', 'SUB-2'),
|
|
$this->refundItem(13, 103, 'SKU-JX', 'SUB-JX'),
|
|
]);
|
|
$db->table('ops_jst_orders')->insert([
|
|
['Id' => 1, 'o_id' => 8901, 'receiver_zip' => 'ZIP-1'],
|
|
['Id' => 2, 'o_id' => 8902, 'receiver_zip' => 'ZIP-2'],
|
|
]);
|
|
$db->table('ops_jst_order_item')->insert([
|
|
['Id' => 1, 'o_id' => 8901, 'outer_oi_id' => 'SUB-1', 'shop_i_id' => 'ITEM-1', 'shop_sku_id' => 'SHOP-SKU-1', 'i_id' => 'STYLE-1', 'sku_id' => 'SKU-1', 'buyer_paid_amount' => 88],
|
|
['Id' => 2, 'o_id' => 8902, 'outer_oi_id' => 'SUB-2', 'shop_i_id' => 'ITEM-2', 'shop_sku_id' => 'SHOP-SKU-2', 'i_id' => 'STYLE-2', 'sku_id' => 'SKU-2', 'buyer_paid_amount' => 99],
|
|
]);
|
|
$db->table('ops_jst_sku')->insert([
|
|
['autoid' => 1, 'sku_id' => 'SKU-1', 'sku_type' => 'normal', 'cost_price' => 20],
|
|
['autoid' => 2, 'sku_id' => 'SKU-2', 'sku_type' => 'normal', 'cost_price' => 30],
|
|
]);
|
|
$db->table('ops_fys_shop_info')->insert([
|
|
['Id' => 1, 'erpShopName' => '一号店', 'responsibleDepartment' => '部门甲'],
|
|
['Id' => 2, 'erpShopName' => '二号店', 'responsibleDepartment' => '部门乙'],
|
|
]);
|
|
|
|
$filters = ['from' => '2026-08-18', 'to' => '2026-08-21', 'order_no' => '', 'shop_name' => '', 'sku_id' => ''];
|
|
$page2 = $this->repository->paginate($filters, 2, 1);
|
|
|
|
$this->assertSame(2, $page2['total']);
|
|
$this->assertSame(11, $page2['data'][0]->Id);
|
|
$this->assertSame('ZIP-1', $page2['data'][0]->originPostcode);
|
|
$this->assertSame('ITEM-1', $page2['data'][0]->shop_i_id);
|
|
$this->assertSame(88, $page2['data'][0]->buyer_paid_amount);
|
|
$this->assertSame(20.0, $page2['data'][0]->unitCostGoods);
|
|
$this->assertSame(40.0, $page2['data'][0]->refundCostGoods);
|
|
$this->assertSame('部门甲', $page2['data'][0]->responsibleDepartment);
|
|
|
|
$filters['shop_name'] = '二号';
|
|
$this->assertSame(1, $this->repository->paginate($filters, 1, 25)['total']);
|
|
$filters['shop_name'] = '';
|
|
$filters['shop_name'] = '%';
|
|
$this->assertSame(0, $this->repository->paginate($filters, 1, 25)['total']);
|
|
$filters['shop_name'] = '';
|
|
$filters['sku_id'] = 'SKU-1';
|
|
$this->assertSame(1, $this->repository->paginate($filters, 1, 25)['total']);
|
|
$filters['sku_id'] = '';
|
|
$filters['order_no'] = '8902';
|
|
$this->assertSame(1, $this->repository->paginate($filters, 1, 25)['total']);
|
|
|
|
$filters['order_no'] = '';
|
|
$exportRows = iterator_to_array($this->repository->exportRows($filters, 1));
|
|
$this->assertCount(2, $exportRows);
|
|
$this->assertSame(12, $exportRows[0]->Id);
|
|
$this->assertSame(11, $exportRows[1]->Id);
|
|
}
|
|
|
|
public function test_sync_status_distinguishes_rotation_from_initial_sync(): void
|
|
{
|
|
$states = $this->repository->states();
|
|
foreach ($states as &$state) {
|
|
$state['status'] = 'running';
|
|
$state['last_succeeded_at'] = '2026-08-21 12:00:00';
|
|
}
|
|
unset($state);
|
|
|
|
$this->assertSame('syncing', $this->repository->syncStatus(
|
|
$states,
|
|
Carbon::parse('2026-08-21 12:05:00'),
|
|
)['code']);
|
|
|
|
$states[NonJingxiRefundMirrorRepository::INFO_DATASET]['initial_completed_at'] = null;
|
|
$this->assertSame('initializing', $this->repository->syncStatus($states)['code']);
|
|
}
|
|
|
|
public function test_query_is_blocked_when_a_required_lookup_index_is_missing(): void
|
|
{
|
|
Schema::connection('business_mirror')->table(
|
|
'ops_jst_return_refund_info',
|
|
static fn (Blueprint $table) => $table->dropIndex(['as_date', 'Id']),
|
|
);
|
|
|
|
$this->assertFalse($this->repository->queryReady());
|
|
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('business_mirror_dataset_not_ready');
|
|
$this->repository->paginate(
|
|
['from' => '2026-08-18', 'to' => '2026-08-21', 'order_no' => '', 'shop_name' => '', 'sku_id' => ''],
|
|
1,
|
|
25,
|
|
);
|
|
}
|
|
|
|
/** @return array<string,array<string,mixed>> */
|
|
private function definitions(): array
|
|
{
|
|
return [
|
|
NonJingxiRefundMirrorRepository::INFO_DATASET => $this->definition('jst_returnAndRefundInfo', 'ops_jst_return_refund_info'),
|
|
NonJingxiRefundMirrorRepository::ITEM_DATASET => $this->definition('jst_returnAndRefundItem', 'ops_jst_return_refund_item'),
|
|
'operations_replica.rpt.jst_Orders' => $this->definition('jst_Orders', 'ops_jst_orders'),
|
|
'operations_replica.rpt.jst_OrderItem' => $this->definition('jst_OrderItem', 'ops_jst_order_item'),
|
|
'operations_replica.rpt.jst_sku' => $this->definition('jst_sku', 'ops_jst_sku'),
|
|
'operations_replica.rpt.fys_shopInfo' => $this->definition('fys_shopInfo', 'ops_fys_shop_info'),
|
|
];
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
private function definition(string $object, string $target): array
|
|
{
|
|
return ['source_key' => 'operations_replica', 'schema' => 'rpt', 'object' => $object, 'target' => $target, 'keys' => ['Id']];
|
|
}
|
|
|
|
private function createTables(): void
|
|
{
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('business_mirror_table_states', function (Blueprint $table): void {
|
|
$table->string('dataset_key')->primary();
|
|
$table->string('status')->default('succeeded');
|
|
$table->dateTime('initial_completed_at')->nullable();
|
|
$table->dateTime('last_succeeded_at')->nullable();
|
|
});
|
|
$schema->create('ops_jst_return_refund_info', function (Blueprint $table): void {
|
|
$table->integer('Id'); $table->integer('as_id'); $table->dateTime('as_date'); $table->string('outer_as_id')->nullable();
|
|
$table->integer('o_id'); $table->string('so_id')->nullable(); $table->string('type')->nullable(); $table->string('node')->nullable();
|
|
$table->string('shop_type')->nullable(); $table->string('shop_name')->nullable(); $table->string('shop_site')->nullable();
|
|
$table->decimal('refund', 18, 2)->nullable(); $table->decimal('receive_refund', 18, 2)->nullable();
|
|
$table->decimal('buyer_apply_refund', 18, 2)->nullable(); $table->dateTime('createTime')->nullable();
|
|
$table->string('status')->nullable(); $table->string('shop_status')->nullable(); $table->dateTime('created')->nullable();
|
|
$table->dateTime('confirm_date')->nullable();
|
|
$table->index(['as_date', 'Id']);
|
|
$table->index(['o_id', 'Id']);
|
|
$table->index(['as_id', 'Id']);
|
|
});
|
|
$schema->create('ops_jst_return_refund_item', function (Blueprint $table): void {
|
|
$table->integer('Id'); $table->integer('as_id'); $table->string('outer_oi_id')->nullable();
|
|
$table->string('shop_sku_id')->nullable(); $table->string('i_id')->nullable(); $table->string('sku_id')->nullable();
|
|
$table->integer('qty')->nullable(); $table->decimal('shop_amount', 18, 2)->nullable();
|
|
$table->string('type')->nullable(); $table->integer('r_qty')->nullable();
|
|
$table->index(['as_id', 'Id']);
|
|
});
|
|
$schema->create('ops_jst_orders', function (Blueprint $table): void {
|
|
$table->integer('Id'); $table->integer('o_id'); $table->string('receiver_zip')->nullable();
|
|
$table->index(['o_id', 'Id']);
|
|
});
|
|
$schema->create('ops_jst_order_item', function (Blueprint $table): void {
|
|
$table->integer('Id'); $table->integer('o_id'); $table->string('outer_oi_id');
|
|
$table->string('shop_i_id')->nullable(); $table->string('shop_sku_id')->nullable(); $table->string('i_id')->nullable();
|
|
$table->string('sku_id')->nullable(); $table->decimal('buyer_paid_amount', 18, 2)->nullable();
|
|
$table->index(['o_id', 'Id']);
|
|
});
|
|
$schema->create('ops_jst_sku', function (Blueprint $table): void {
|
|
$table->integer('autoid'); $table->string('sku_id'); $table->string('sku_type'); $table->decimal('cost_price', 18, 2)->nullable();
|
|
});
|
|
$schema->create('ops_fys_shop_info', function (Blueprint $table): void {
|
|
$table->integer('Id'); $table->string('erpShopName'); $table->string('responsibleDepartment')->nullable();
|
|
});
|
|
}
|
|
|
|
private function markReady(): void
|
|
{
|
|
foreach (array_keys($this->definitions()) as $key) {
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')->insert([
|
|
'dataset_key' => $key,
|
|
'status' => 'succeeded',
|
|
'initial_completed_at' => '2026-08-15 00:00:00',
|
|
'last_succeeded_at' => '2026-08-21 12:00:00',
|
|
]);
|
|
}
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
private function refundInfo(int $id, int $asId, string $date, string $platform, string $shopName): array
|
|
{
|
|
return [
|
|
'Id' => $id, 'as_id' => $asId, 'as_date' => $date, 'outer_as_id' => 'AS-' . $asId,
|
|
'o_id' => 8900 + $id, 'so_id' => 'SO-' . $id, 'type' => '退款', 'node' => '完成',
|
|
'shop_type' => '自营', 'shop_name' => $shopName, 'shop_site' => $platform,
|
|
'refund' => 100, 'receive_refund' => 80, 'buyer_apply_refund' => 100,
|
|
'createTime' => $date, 'status' => '完成', 'shop_status' => '完成',
|
|
'created' => $date, 'confirm_date' => $date,
|
|
];
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
private function refundItem(int $id, int $asId, string $skuId, string $outerOrderItemId): array
|
|
{
|
|
return [
|
|
'Id' => $id, 'as_id' => $asId, 'outer_oi_id' => $outerOrderItemId,
|
|
'shop_sku_id' => 'SHOP-' . $skuId, 'i_id' => 'STYLE-' . $id, 'sku_id' => $skuId,
|
|
'qty' => 2, 'shop_amount' => 80, 'type' => '退货退款', 'r_qty' => 2,
|
|
];
|
|
}
|
|
}
|