272 lines
10 KiB
PHP
272 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Console;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\EcommerceFinance\Services\DingProcessDetailMirrorImporter;
|
|
use RuntimeException;
|
|
use Tests\TestCase;
|
|
|
|
class ImportDingProcessDetailsTest extends TestCase
|
|
{
|
|
private string $fixture;
|
|
|
|
/** @var list<string> */
|
|
private array $tempFiles = [];
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'database.connections.business_mirror' => [
|
|
'driver' => 'sqlite',
|
|
'database' => ':memory:',
|
|
'prefix' => '',
|
|
],
|
|
'upstream_mirrors.connection' => 'business_mirror',
|
|
'upstream_mirrors.ding_process_details.scope' => 'personal_permission',
|
|
]);
|
|
DB::purge('business_mirror');
|
|
$this->createMirrorTables();
|
|
|
|
$this->fixture = base_path('tests/Fixtures/ding_process_details_sample.csv');
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
foreach ($this->tempFiles as $file) {
|
|
if (is_file($file)) {
|
|
unlink($file);
|
|
}
|
|
}
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_imports_csv_and_publishes_mirror_state(): void
|
|
{
|
|
$result = app(DingProcessDetailMirrorImporter::class)
|
|
->import($this->fixture, 'personal_permission', true);
|
|
|
|
$this->assertSame('succeeded', $result['status']);
|
|
$this->assertSame(4, $result['rows']);
|
|
$this->assertSame(0, $result['invalid']);
|
|
$this->assertSame(0, $result['placeholders']);
|
|
$this->assertSame(4, DB::connection('business_mirror')
|
|
->table('ding_process_detail_mirror')->count());
|
|
|
|
$row = DB::connection('business_mirror')
|
|
->table('ding_process_detail_mirror')
|
|
->where('business_id', '202601011200000000001')
|
|
->where('line_no', 1)
|
|
->first();
|
|
|
|
$this->assertNotNull($row);
|
|
$this->assertSame('张三', $row->originator_name);
|
|
$this->assertSame('已完成', $row->status);
|
|
$this->assertSame('公摊日常报销', $row->document_type);
|
|
$this->assertSame('5602.04', $row->account_code);
|
|
$this->assertSame('管理费用_办公费', $row->account_name);
|
|
$this->assertEqualsWithDelta(100.0, (float) $row->amount, 0.001);
|
|
$this->assertSame('202601', $row->business_month);
|
|
$this->assertSame('personal_permission', $row->source_scope);
|
|
|
|
$state = DB::connection('business_mirror')
|
|
->table('business_mirror_table_states')
|
|
->where('dataset_key', 'ding.process_details.personal_permission')
|
|
->first();
|
|
|
|
$this->assertNotNull($state);
|
|
$this->assertSame('succeeded', $state->status);
|
|
$this->assertSame('ding_process_detail_mirror', $state->target_table);
|
|
$this->assertNotNull($state->initial_completed_at);
|
|
$this->assertSame(4, (int) $state->mirror_row_count);
|
|
}
|
|
|
|
public function test_import_is_idempotent_by_business_id_and_line_number(): void
|
|
{
|
|
$importer = app(DingProcessDetailMirrorImporter::class);
|
|
$importer->import($this->fixture, 'personal_permission', true);
|
|
$importer->import($this->fixture, 'personal_permission', false);
|
|
|
|
$this->assertSame(4, DB::connection('business_mirror')
|
|
->table('ding_process_detail_mirror')->count());
|
|
}
|
|
|
|
public function test_replace_removes_rows_that_are_not_in_the_new_file(): void
|
|
{
|
|
$importer = app(DingProcessDetailMirrorImporter::class);
|
|
$importer->import($this->fixture, 'personal_permission', true);
|
|
|
|
$replacement = $this->makeCsv([[
|
|
'1',
|
|
'202603011200000000003',
|
|
'王五',
|
|
'已完成',
|
|
'店铺日常报销',
|
|
'替换后的流程',
|
|
'1',
|
|
'5601.01',
|
|
'销售费用',
|
|
'10.00',
|
|
'1002.01.003',
|
|
'银行-测试3',
|
|
'10.00',
|
|
'银行卡',
|
|
'2026-03-02 00:00',
|
|
]]);
|
|
|
|
$importer->import($replacement, 'personal_permission', true);
|
|
|
|
$this->assertSame(1, DB::connection('business_mirror')
|
|
->table('ding_process_detail_mirror')->count());
|
|
$this->assertSame(
|
|
'202603011200000000003',
|
|
(string) DB::connection('business_mirror')
|
|
->table('ding_process_detail_mirror')
|
|
->value('business_id'),
|
|
);
|
|
}
|
|
|
|
public function test_missing_headers_does_not_delete_existing_rows(): void
|
|
{
|
|
$importer = app(DingProcessDetailMirrorImporter::class);
|
|
$importer->import($this->fixture, 'personal_permission', true);
|
|
|
|
$path = tempnam(sys_get_temp_dir(), 'ding-process-bad-');
|
|
file_put_contents($path, "序号,审批编号\n1,202603011200000000003\n");
|
|
$this->tempFiles[] = $path;
|
|
|
|
try {
|
|
$importer->import($path, 'personal_permission', true);
|
|
$this->fail('缺少表头时应抛出异常。');
|
|
} catch (RuntimeException $exception) {
|
|
$this->assertStringContainsString('缺少列', $exception->getMessage());
|
|
}
|
|
|
|
$this->assertSame(4, DB::connection('business_mirror')
|
|
->table('ding_process_detail_mirror')->count());
|
|
}
|
|
|
|
public function test_placeholder_rows_are_skipped_and_the_better_duplicate_wins(): void
|
|
{
|
|
$path = $this->makeCsv([
|
|
[
|
|
'1', '202604011200000000004', '赵六', '已完成', '公摊日常报销', '真实费用',
|
|
'1', '5602.04', '管理费用_办公费', '100.00', '1002.01.001', '华夏银行-测试',
|
|
'100.00', '银行卡', '2026-04-02 00:00',
|
|
],
|
|
[
|
|
'2', '202604011200000000004', '赵六', '已完成', '公摊日常报销', '251',
|
|
'1', '251', '251', '', '251', '251', '251', '251', '251',
|
|
],
|
|
[
|
|
'3', '202604011200000000004', '赵六', '已完成', '公摊日常报销', '251',
|
|
'1', '1221.05', '251', '0.00', '1002.01.001', '华夏银行-测试',
|
|
'100.00', '银行卡', '2026-04-02 00:00',
|
|
],
|
|
[
|
|
'4', '202604021200000000005', '钱七', '已完成', '店铺日常报销', '251',
|
|
'1', '251', '251', '', '251', '251', '251', '251', '251',
|
|
],
|
|
]);
|
|
|
|
$result = app(DingProcessDetailMirrorImporter::class)
|
|
->import($path, 'personal_permission', true);
|
|
|
|
$this->assertSame(1, $result['rows']);
|
|
$this->assertSame(2, $result['placeholders']);
|
|
$this->assertSame(1, $result['duplicates']);
|
|
$row = DB::connection('business_mirror')
|
|
->table('ding_process_detail_mirror')
|
|
->first();
|
|
$this->assertSame('5602.04', $row->account_code);
|
|
$this->assertSame('管理费用_办公费', $row->account_name);
|
|
$this->assertEqualsWithDelta(100.0, (float) $row->amount, 0.001);
|
|
}
|
|
|
|
public function test_artisan_command_imports_the_file(): void
|
|
{
|
|
$this->artisan('ding:import-process-details', [
|
|
'file' => $this->fixture,
|
|
'--scope' => 'personal_permission',
|
|
'--replace' => true,
|
|
])->assertSuccessful();
|
|
|
|
$this->assertSame(4, DB::connection('business_mirror')
|
|
->table('ding_process_detail_mirror')->count());
|
|
}
|
|
|
|
private function createMirrorTables(): void
|
|
{
|
|
Schema::connection('business_mirror')->create(
|
|
'business_mirror_table_states',
|
|
function (Blueprint $table): void {
|
|
$table->string('dataset_key')->primary();
|
|
$table->string('source_key');
|
|
$table->string('source_object');
|
|
$table->string('target_table');
|
|
$table->string('status')->default('pending');
|
|
$table->string('run_mode')->nullable();
|
|
$table->string('generation')->nullable();
|
|
$table->json('cursor')->nullable();
|
|
$table->json('high_watermark')->nullable();
|
|
$table->dateTime('window_started_at')->nullable();
|
|
$table->dateTime('window_ends_at')->nullable();
|
|
$table->dateTime('initial_completed_at')->nullable();
|
|
$table->dateTime('last_full_succeeded_at')->nullable();
|
|
$table->dateTime('last_attempted_at')->nullable();
|
|
$table->dateTime('last_succeeded_at')->nullable();
|
|
$table->unsignedBigInteger('last_upserted_rows')->default(0);
|
|
$table->unsignedBigInteger('mirror_row_count')->default(0);
|
|
$table->string('last_error_code')->nullable();
|
|
$table->timestamps();
|
|
},
|
|
);
|
|
|
|
Schema::connection('business_mirror')->create(
|
|
'ding_process_detail_mirror',
|
|
function (Blueprint $table): void {
|
|
$table->bigIncrements('id');
|
|
$table->string('business_id', 32);
|
|
$table->unsignedInteger('line_no');
|
|
$table->string('business_month', 6)->index();
|
|
$table->string('originator_name', 100)->nullable()->index();
|
|
$table->string('status', 20)->nullable()->index();
|
|
$table->string('document_type', 100)->nullable()->index();
|
|
$table->text('purpose')->nullable();
|
|
$table->string('account_code', 64)->nullable();
|
|
$table->string('account_name', 255)->nullable();
|
|
$table->decimal('amount', 18, 2)->nullable();
|
|
$table->string('bank_account_code', 255)->nullable();
|
|
$table->string('bank_account_name', 255)->nullable();
|
|
$table->decimal('bank_amount', 18, 2)->nullable();
|
|
$table->string('payment_method', 50)->nullable();
|
|
$table->string('paid_at', 50)->nullable();
|
|
$table->string('source_scope', 40)->default('personal_permission')->index();
|
|
$table->dateTime('synced_at', 6);
|
|
$table->timestamps();
|
|
$table->unique(['business_id', 'line_no']);
|
|
},
|
|
);
|
|
}
|
|
|
|
/** @param list<list<string>> $rows */
|
|
private function makeCsv(array $rows): string
|
|
{
|
|
$path = tempnam(sys_get_temp_dir(), 'ding-process-');
|
|
$handle = fopen($path, 'wb');
|
|
fputcsv($handle, DingProcessDetailMirrorImporter::HEADERS);
|
|
foreach ($rows as $row) {
|
|
fputcsv($handle, $row);
|
|
}
|
|
fclose($handle);
|
|
$this->tempFiles[] = $path;
|
|
|
|
return $path;
|
|
}
|
|
}
|