311 lines
16 KiB
PHP
311 lines
16 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\SmallPaymentWorkOrderMirrorRepository;
|
|
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
|
|
use Tests\TestCase;
|
|
|
|
class SmallPaymentWorkOrderMirrorRepositoryTest extends TestCase
|
|
{
|
|
private SmallPaymentWorkOrderMirrorRepository $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' => [
|
|
SmallPaymentWorkOrderMirrorRepository::WORK_ORDER_DATASET => [
|
|
'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'jst_workOrderInfo',
|
|
'target' => 'fixture_work_orders', 'keys' => ['Id'], 'watermark' => 'updateTime',
|
|
'indexes' => [
|
|
['woTypeId', 'woNo', 'Id'],
|
|
['woTypeId', 'completeTime', 'woNo', 'Id'],
|
|
],
|
|
],
|
|
SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET => [
|
|
'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'jst_small_payment_order_map',
|
|
'target' => 'fixture_payment_map', 'keys' => ['woNo'], 'watermark' => 'updated_at',
|
|
'indexes' => [['woTypeId', 'woNo']],
|
|
],
|
|
],
|
|
]);
|
|
Cache::setDefaultDriver('array');
|
|
Cache::flush();
|
|
DB::purge('business_mirror');
|
|
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('business_mirror_table_states', function (Blueprint $table): void {
|
|
$table->string('dataset_key')->primary();
|
|
$table->string('status')->nullable();
|
|
$table->dateTime('initial_completed_at')->nullable();
|
|
$table->dateTime('last_succeeded_at')->nullable();
|
|
});
|
|
$schema->create('fixture_work_orders', function (Blueprint $table): void {
|
|
$table->integer('Id');
|
|
foreach (['woNo', 'woTypeId', 'title', 'content', 'status', 'grade', 'createby', 'executors', 'completer', 'completeTime', 'created', 'updated', 'updateTime', 'businessFieldList'] as $column) {
|
|
$table->text($column)->nullable();
|
|
}
|
|
$table->integer('urging')->nullable();
|
|
$table->index(['woTypeId', 'woNo', 'Id']);
|
|
$table->index(['woTypeId', 'completeTime', 'woNo', 'Id']);
|
|
});
|
|
$schema->create('fixture_payment_map', function (Blueprint $table): void {
|
|
foreach (['woNo', 'woTypeId', 'transaction_no', 'shop_name', 'payment_reason', 'title', 'status', 'work_order_create_time', 'work_order_update_time', 'parse_status', 'parse_error', 'updated_at'] as $column) {
|
|
$table->text($column)->nullable();
|
|
}
|
|
$table->index(['woTypeId', 'woNo']);
|
|
});
|
|
|
|
foreach (array_keys(config('upstream_mirrors.datasets')) as $key) {
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')->insert([
|
|
'dataset_key' => $key,
|
|
'status' => 'succeeded',
|
|
'initial_completed_at' => '2026-09-01 00:00:00',
|
|
'last_succeeded_at' => '2026-09-03 12:00:00',
|
|
]);
|
|
}
|
|
|
|
$type = SmallPaymentWorkOrderMirrorRepository::SMALL_PAYMENT_WORK_TYPE_ID;
|
|
$workOrderDefaults = array_fill_keys([
|
|
'Id', 'woNo', 'woTypeId', 'title', 'content', 'status', 'grade',
|
|
'urging', 'createby', 'executors', 'completer', 'completeTime',
|
|
'created', 'updated', 'updateTime', 'businessFieldList',
|
|
], null);
|
|
$workOrderRows = [
|
|
[
|
|
'Id' => 1, 'woNo' => 'GD-001', 'woTypeId' => $type,
|
|
'title' => '小额打款一', 'content' => '售后补偿', 'status' => 'CONFIRMED',
|
|
'grade' => '普通', 'urging' => 1, 'createby' => '客服甲', 'executors' => '财务甲',
|
|
'completer' => '财务甲', 'completeTime' => '2026-09-02 11:00:00',
|
|
'created' => '2026-09-02 09:00:00', 'updated' => '2026-09-02 11:00:00',
|
|
'updateTime' => '2026-09-02 11:01:00',
|
|
'businessFieldList' => json_encode([
|
|
['fieldId' => 'SELECTField1413856620250724113227', 'value' => '支付宝账号'],
|
|
['fieldName' => '支付宝批次号', 'fieldValue' => 'BATCH-001'],
|
|
['fieldName' => '打款金额', 'fieldValue' => '50.005'],
|
|
['fieldName' => '买家账号', 'fieldValue' => 'buyer-one'],
|
|
['fieldName' => '支付宝账号', 'fieldValue' => 'account-one'],
|
|
['fieldName' => '支付宝姓名', 'fieldValue' => '测试买家'],
|
|
['fieldName' => '商品编码', 'fieldValue' => 'ITEM-001'],
|
|
['fieldName' => 'SKU编码', 'fieldValue' => 'SKU-001'],
|
|
['fieldName' => '平台', 'fieldValue' => '京东'],
|
|
['fieldName' => '实际已付金额', 'fieldValue' => '199.90'],
|
|
['fieldName' => '支付时间', 'fieldValue' => '2026-09-02 10:30:00'],
|
|
], JSON_UNESCAPED_UNICODE),
|
|
],
|
|
[
|
|
'Id' => 2, 'woNo' => 'GD-002', 'woTypeId' => $type,
|
|
'title' => '小额打款二', 'content' => '少收多收', 'status' => 'CONFIRMED',
|
|
'grade' => '紧急', 'urging' => 0, 'createby' => '客服乙',
|
|
'completer' => '财务乙', 'completeTime' => '2026-09-03 10:00:00',
|
|
'created' => '2026-08-31 09:00:00', 'updated' => '2026-09-03 10:00:00',
|
|
'updateTime' => '2026-09-03 09:31:00',
|
|
'businessFieldList' => json_encode([
|
|
['fieldId' => 'SELECTField1413856620250724113227', 'value' => '银行卡'],
|
|
['fieldId' => 'shopField', 'type' => 'SELECT', 'value' => '18526685', 'valueLabel' => '天猫-亿家达旗舰店'],
|
|
['fieldId' => 'alipayAccountField', 'type' => 'TEXT', 'value' => 'account-two'],
|
|
['fieldId' => 'alipayNameField', 'type' => 'TEXT', 'value' => '测试买家二'],
|
|
['fieldId' => 'buyerIdField', 'type' => 'TEXT', 'value' => 'buyer-two'],
|
|
['childFieldList' => [[
|
|
['fieldId' => 'iid', 'type' => 'TEXT', 'value' => 'ITEM-002'],
|
|
['fieldId' => 'skuId', 'type' => 'TEXT', 'value' => 'SKU-002'],
|
|
]], 'fieldId' => 'itemInfo', 'type' => 'FORM'],
|
|
['fieldId' => 'actualPaidAmountField', 'type' => 'MONEY', 'value' => '188.88'],
|
|
['fieldId' => 'platform', 'type' => 'SELECT', 'value' => '淘宝天猫'],
|
|
['fieldId' => 'payDateField', 'type' => 'DATE', 'value' => '2026-09-03 08:30:00'],
|
|
['fieldId' => 'amountField', 'type' => 'MONEY', 'value' => '20'],
|
|
['fieldId' => 'orderField', 'type' => 'TEXT', 'value' => 'ORDER-002'],
|
|
], JSON_UNESCAPED_UNICODE),
|
|
],
|
|
[
|
|
'Id' => 3, 'woNo' => 'OTHER-001', 'woTypeId' => 'OTHER',
|
|
'title' => '其他工单', 'status' => '已完成',
|
|
'created' => '2026-09-03 10:00:00', 'updated' => '2026-09-03 10:00:00',
|
|
'updateTime' => '2026-09-03 10:00:00', 'businessFieldList' => '{}',
|
|
],
|
|
];
|
|
DB::connection('business_mirror')->table('fixture_work_orders')->insert(array_map(
|
|
static fn (array $row): array => array_replace($workOrderDefaults, $row),
|
|
$workOrderRows,
|
|
));
|
|
DB::connection('business_mirror')->table('fixture_payment_map')->insert([
|
|
[
|
|
'woNo' => 'GD-001', 'woTypeId' => $type, 'transaction_no' => 'ORDER-001',
|
|
'shop_name' => '京东旗舰店', 'payment_reason' => '售后补偿', 'title' => '小额打款一',
|
|
'status' => '已完成', 'work_order_create_time' => '2026-09-02 09:00:00',
|
|
'work_order_update_time' => '2026-09-02 11:00:00', 'parse_status' => 'success',
|
|
'updated_at' => '2026-09-02 11:01:00',
|
|
],
|
|
[
|
|
'woNo' => 'GD-002', 'woTypeId' => $type, 'transaction_no' => 'ORDER-002',
|
|
'shop_name' => '天猫旗舰店', 'payment_reason' => '少收多收', 'title' => '小额打款二',
|
|
'status' => 'CONFIRMED', 'work_order_create_time' => '2026-08-31 09:00:00',
|
|
'work_order_update_time' => '2026-09-03 09:30:00', 'parse_status' => 'success',
|
|
'updated_at' => '2026-09-03 09:31:00',
|
|
],
|
|
[
|
|
'woNo' => 'OTHER-001', 'woTypeId' => 'OTHER', 'transaction_no' => 'BATCH-003',
|
|
'shop_name' => '其他店铺', 'payment_reason' => '其他', 'title' => '其他工单',
|
|
'status' => '已完成', 'work_order_create_time' => '2026-09-03 10:00:00',
|
|
'work_order_update_time' => '2026-09-03 10:00:00', 'parse_status' => 'success',
|
|
'updated_at' => '2026-09-03 10:00:00',
|
|
],
|
|
]);
|
|
|
|
$this->repository = new SmallPaymentWorkOrderMirrorRepository(
|
|
new BusinessMirrorDatasetRegistry(),
|
|
new GenericBusinessMirrorRepository(app('db')),
|
|
);
|
|
}
|
|
|
|
public function test_filters_paginates_and_parses_small_payment_business_fields(): void
|
|
{
|
|
$result = $this->repository->paginate($this->filters([
|
|
'shop_name' => '京东',
|
|
'online_order_no' => 'ORDER-001',
|
|
'status' => '已完成',
|
|
]), 1, 25);
|
|
|
|
$this->assertSame(1, $result['total']);
|
|
$this->assertSame('50.01', $result['page_payment_amount']);
|
|
$this->assertSame('50.01', $result['filtered_payment_amount']);
|
|
$this->assertSame('GD-001', $result['data'][0]['work_order_no']);
|
|
$this->assertSame('BATCH-001', $result['data'][0]['alipay_batch_no']);
|
|
$this->assertSame('已完成', $result['data'][0]['work_order_status']);
|
|
$this->assertSame('小额打款', $result['data'][0]['work_order_type']);
|
|
$this->assertSame('支付宝已打款', $result['data'][0]['payment_status']);
|
|
$this->assertSame('ORDER-001', $result['data'][0]['online_order_no']);
|
|
$this->assertSame('SKU-001', $result['data'][0]['sku_code']);
|
|
$this->assertSame('199.90', $result['data'][0]['actual_paid_amount']);
|
|
$this->assertArrayNotHasKey('business_field_list', $result['data'][0]);
|
|
}
|
|
|
|
public function test_stable_order_second_page_and_exact_work_type_filter(): void
|
|
{
|
|
$result = $this->repository->paginate($this->filters(), 2, 1);
|
|
|
|
$this->assertSame(2, $result['total']);
|
|
$this->assertSame('GD-001', $result['data'][0]['work_order_no']);
|
|
$this->assertSame('50.01', $result['page_payment_amount']);
|
|
$this->assertSame('70.01', $result['filtered_payment_amount']);
|
|
}
|
|
|
|
public function test_export_uses_same_filters_and_parsed_shape(): void
|
|
{
|
|
$rows = iterator_to_array($this->repository->exportRows($this->filters([
|
|
'transaction_no' => 'BATCH-001',
|
|
'payment_reason' => '售后',
|
|
]), 1));
|
|
|
|
$this->assertCount(1, $rows);
|
|
$this->assertSame('GD-001', $rows[0]['work_order_no']);
|
|
$this->assertSame('BATCH-001', $rows[0]['alipay_batch_no']);
|
|
$this->assertSame('ORDER-001', $rows[0]['online_order_no']);
|
|
$this->assertSame('已完成', $rows[0]['work_order_status']);
|
|
$this->assertSame('支付宝已打款', $rows[0]['payment_status']);
|
|
}
|
|
|
|
public function test_completion_date_and_chinese_status_follow_upstream_semantics(): void
|
|
{
|
|
$result = $this->repository->paginate($this->filters([
|
|
'from' => '2026-09-03',
|
|
'to' => '2026-09-03',
|
|
'status' => '已完成',
|
|
]), 1, 25);
|
|
|
|
$this->assertSame(1, $result['total']);
|
|
$this->assertSame('GD-002', $result['data'][0]['work_order_no']);
|
|
$this->assertSame('2026-08-31 09:00:00', $result['data'][0]['work_order_created_at']);
|
|
$this->assertSame('2026-09-03 10:00:00', $result['data'][0]['completed_at']);
|
|
$this->assertSame('已完成', $result['data'][0]['work_order_status']);
|
|
$this->assertSame('银行卡已打款', $result['data'][0]['payment_status']);
|
|
$this->assertSame('', $result['data'][0]['alipay_batch_no']);
|
|
}
|
|
|
|
public function test_derived_payment_status_filter_matches_payment_method_field(): void
|
|
{
|
|
$result = $this->repository->paginate($this->filters([
|
|
'status' => '支付宝已打款',
|
|
]), 1, 25);
|
|
|
|
$this->assertSame(1, $result['total']);
|
|
$this->assertSame('GD-001', $result['data'][0]['work_order_no']);
|
|
$this->assertSame('支付宝已打款', $result['data'][0]['payment_status']);
|
|
}
|
|
|
|
public function test_datetime_minute_bounds_include_the_selected_end_minute(): void
|
|
{
|
|
$result = $this->repository->paginate($this->filters([
|
|
'from' => '2026-09-03T10:00',
|
|
'to' => '2026-09-03T10:00',
|
|
]), 1, 25);
|
|
|
|
$this->assertSame(1, $result['total']);
|
|
$this->assertSame('GD-002', $result['data'][0]['work_order_no']);
|
|
}
|
|
|
|
public function test_summary_and_filter_options_reuse_cached_results(): void
|
|
{
|
|
$scanQueries = [];
|
|
DB::connection('business_mirror')->listen(static function ($query) use (&$scanQueries): void {
|
|
$sql = strtolower((string) $query->sql);
|
|
if (str_contains($sql, 'businessfieldlist') || str_contains($sql, 'select distinct')) {
|
|
$scanQueries[] = $sql;
|
|
}
|
|
});
|
|
|
|
$filters = $this->filters();
|
|
$firstAmount = $this->repository->filteredPaymentAmount($filters);
|
|
$firstOptions = $this->repository->filterOptions();
|
|
$queriesAfterFirstRead = count($scanQueries);
|
|
$secondAmount = $this->repository->filteredPaymentAmount($filters);
|
|
$secondOptions = $this->repository->filterOptions();
|
|
|
|
$this->assertSame('70.01', $firstAmount);
|
|
$this->assertSame($firstAmount, $secondAmount);
|
|
$this->assertSame($firstOptions, $secondOptions);
|
|
$this->assertSame(['京东旗舰店', '天猫旗舰店'], $firstOptions['shops']);
|
|
$this->assertSame(['售后补偿', '少收多收'], $firstOptions['payment_reasons']);
|
|
$this->assertGreaterThan(0, $queriesAfterFirstRead);
|
|
$this->assertSame($queriesAfterFirstRead, count($scanQueries));
|
|
}
|
|
|
|
public function test_unready_state_is_explicit(): void
|
|
{
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')
|
|
->where('dataset_key', SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET)
|
|
->update(['initial_completed_at' => null, 'status' => 'running']);
|
|
|
|
$states = $this->repository->states();
|
|
$status = $this->repository->syncStatus($states);
|
|
|
|
$this->assertFalse($this->repository->ready());
|
|
$this->assertSame('initializing', $status['code']);
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->repository->paginate($this->filters(), 1, 25);
|
|
}
|
|
|
|
/** @param array<string,string> $overrides @return array<string,string> */
|
|
private function filters(array $overrides = []): array
|
|
{
|
|
return array_replace([
|
|
'from' => '2026-09-01', 'to' => '2026-09-30',
|
|
'work_order_no' => '', 'shop_name' => '', 'transaction_no' => '',
|
|
'online_order_no' => '', 'status' => '', 'payment_reason' => '',
|
|
], $overrides);
|
|
}
|
|
}
|