CaiWuXiTong/tests/Unit/EcommerceFinance/SmallPaymentWorkOrderMirrorRepositoryTest.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);
}
}