154 lines
6.6 KiB
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;
|
|
}
|
|
}
|