CaiWuXiTong/tests/Feature/Console/StoreReportDataImportTest.php

154 lines
6.6 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\Repositories\StoreReportRepository;
use Modules\EcommerceFinance\Services\StoreReportDataImporter;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use RuntimeException;
use Tests\TestCase;
class StoreReportDataImportTest extends TestCase
{
/** @var string[] */
private array $temporaryFiles = [];
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->createTables();
}
protected function tearDown(): void
{
foreach ($this->temporaryFiles as $file) {
@unlink($file);
}
parent::tearDown();
}
public function test_it_imports_platform_income_and_lifecycles_into_shared_generations(): void
{
$bills = $this->xlsx('抖音平台原始账单', [
['店铺', '记录编码', '结算时间', '发生时间', '收入', '支出', '账单项目'],
['店铺A', 'BILL-1', '2026-08-02 10:00:00', '2026-08-01 09:00:00', '100.25', '2.5', '订单收入'],
['店铺A', 'BILL-2', null, '2026-08-03 11:00:00', '50', '0', '订单收入'],
]);
$lifecycles = $this->xlsx('店铺生命周期', [
['ERP系统店铺名称', '公司名称', '生效时间', '失效时间'],
['店铺A', '主体甲', '2026-01-01', '存续中'],
]);
$importer = app(StoreReportDataImporter::class);
$billResult = $importer->importBills($bills, 100);
$lifecycleResult = $importer->importLifecycles($lifecycles, 100);
$this->assertSame(2, $billResult['row_count']);
$this->assertSame(1, $lifecycleResult['row_count']);
$this->assertDatabaseHas('store_report_bill_rows', [
'record_code' => 'BILL-1', 'occurred_at' => '2026-08-02 10:00:00', 'income' => 100.25,
], 'business_mirror');
$this->assertDatabaseHas('store_report_bill_rows', [
'record_code' => 'BILL-2', 'occurred_at' => '2026-08-03 11:00:00', 'income' => 50,
], 'business_mirror');
$this->assertDatabaseHas('store_report_import_states', [
'dataset_key' => StoreReportRepository::BILLS_DATASET, 'status' => 'succeeded', 'row_count' => 2,
], 'business_mirror');
$this->assertDatabaseHas('store_report_lifecycle_rows', [
'erp_store_name' => '店铺A', 'company_name' => '主体甲', 'expires_at' => null,
], 'business_mirror');
}
public function test_failed_reimport_keeps_the_previous_active_generation(): void
{
$valid = $this->xlsx('拼多多平台原始账单', [
['店铺', '记录编码', '发生时间', '收入金额', '支出金额'],
['店铺A', 'PDD-1', '2026-08-01', '10', '0'],
]);
$importer = app(StoreReportDataImporter::class);
$first = $importer->importBills($valid, 100);
$invalid = $this->xlsx('拼多多错误账单', [
['店铺', '记录编码', '发生时间', '收入金额'],
['店铺A', 'PDD-2', '不是时间', '20'],
]);
try {
$importer->importBills($invalid, 100);
$this->fail('无效时间应导致导入失败。');
} catch (RuntimeException) {
$state = DB::connection('business_mirror')->table('store_report_import_states')
->where('dataset_key', StoreReportRepository::BILLS_DATASET)->first();
$this->assertSame('failed', $state->status);
$this->assertSame($first['generation'], $state->active_generation);
$this->assertSame(1, DB::connection('business_mirror')->table('store_report_bill_rows')->count());
}
}
private function createTables(): void
{
$schema = Schema::connection('business_mirror');
$schema->create('store_report_import_states', function (Blueprint $table): void {
$table->string('dataset_key')->primary();
$table->string('status')->default('pending');
$table->string('active_generation')->nullable();
$table->string('pending_generation')->nullable();
$table->unsignedBigInteger('row_count')->default(0);
$table->string('source_sha256')->nullable();
$table->text('source_files_json')->nullable();
$table->dateTime('last_attempted_at')->nullable();
$table->dateTime('last_succeeded_at')->nullable();
$table->string('last_error_code')->nullable();
$table->timestamps();
});
$schema->create('store_report_bill_rows', function (Blueprint $table): void {
$table->increments('id');
$table->string('generation');
$table->string('platform');
$table->string('platform_label');
$table->string('store_name');
$table->dateTime('occurred_at')->nullable();
$table->decimal('income', 20, 4);
$table->decimal('expense', 20, 4);
$table->string('bill_item')->nullable();
$table->string('record_code');
$table->string('source_file');
$table->string('row_hash');
$table->dateTime('imported_at');
$table->unique(['generation', 'platform', 'record_code']);
});
$schema->create('store_report_lifecycle_rows', function (Blueprint $table): void {
$table->increments('id');
$table->string('generation');
$table->string('business_key');
$table->string('erp_store_name');
$table->string('company_name')->nullable();
$table->dateTime('effective_at')->nullable();
$table->dateTime('expires_at')->nullable();
$table->string('source_file');
$table->string('row_hash');
$table->dateTime('imported_at');
$table->unique(['generation', 'business_key']);
});
}
private function xlsx(string $name, array $rows): string
{
$path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $name . '-' . uniqid() . '.xlsx';
$spreadsheet = new Spreadsheet();
$spreadsheet->getActiveSheet()->fromArray($rows);
(new Xlsx($spreadsheet))->save($path);
$spreadsheet->disconnectWorksheets();
$this->temporaryFiles[] = $path;
return $path;
}
}