[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', ]); DB::purge('business_mirror'); $this->createTable(); $this->workbookPath = sys_get_temp_dir() . '/alibaba-account-balance-' . uniqid('', true) . '.xlsx'; } protected function tearDown(): void { if (isset($this->workbookPath) && is_file($this->workbookPath)) { unlink($this->workbookPath); } DB::purge('business_mirror'); parent::tearDown(); } public function testItImportsUpdatesFiltersAndPaginatesBalanceSnapshots(): void { $this->writeWorkbook([ ['2026-06-30', '阿里店甲', null, null, null, 4142.96, null, null, null, null, null], ['2026-06-30', '阿里店乙', 100, 50, 20, 130, 130, 0, '', '已核对', '测试'], ]); $importer = app(AlibabaAccountBalanceWorkbookImporter::class); $first = $importer->import($this->workbookPath, '阿里巴巴余额.xlsx'); $second = $importer->import($this->workbookPath, '阿里巴巴余额.xlsx'); $this->assertSame(['created' => 2, 'updated' => 0, 'skipped' => 0, 'total' => 2], $first); $this->assertSame(['created' => 0, 'updated' => 2, 'skipped' => 0, 'total' => 2], $second); $this->assertSame(2, DB::connection('business_mirror')->table(AlibabaAccountBalanceRepository::TABLE)->count()); $repository = app(AlibabaAccountBalanceRepository::class); $result = $repository->paginateMonthlyBalances('阿里店甲', '2026-06', 1, 25); $this->assertTrue($repository->usesBusinessMirror()); $this->assertSame(['阿里店乙', '阿里店甲'], $repository->storeOptions()); $this->assertSame(1, $result['total']); $this->assertSame('2026-06-30', $result['rows'][0]['date']); $this->assertSame('阿里店甲', $result['rows'][0]['store_name']); $this->assertSame(4142.96, $result['rows'][0]['closing_balance']); $this->assertNull($result['rows'][0]['opening_balance']); } public function testItRejectsNonMonthEndDatesBeforeWriting(): void { $this->writeWorkbook([ ['2026-06-29', '阿里店甲', null, null, null, 10, null, null, null, null, null], ]); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('必须是月末日期'); app(AlibabaAccountBalanceWorkbookImporter::class)->import($this->workbookPath); } /** @param array> $rows */ private function writeWorkbook(array $rows): void { $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('阿里巴巴账户'); $sheet->fromArray([[ '日期', '店铺名称', '期初余额', '本期收入', '本期支出', '期末余额', '系统期末余额', '余额差异', '差异原因', '核对状态', '备注', ], ...$rows]); (new Xlsx($spreadsheet))->save($this->workbookPath); $spreadsheet->disconnectWorksheets(); } private function createTable(): void { Schema::connection('business_mirror')->create(AlibabaAccountBalanceRepository::TABLE, function (Blueprint $table): void { $table->id(); $table->date('balance_date'); $table->string('store_name'); $table->decimal('opening_balance', 20, 6)->nullable(); $table->decimal('income_total', 20, 6)->nullable(); $table->decimal('expense_total', 20, 6)->nullable(); $table->decimal('closing_balance', 20, 6)->nullable(); $table->decimal('system_closing_balance', 20, 6)->nullable(); $table->decimal('balance_difference', 20, 6)->nullable(); $table->text('difference_reason')->nullable(); $table->string('verification_status')->nullable(); $table->text('notes')->nullable(); $table->string('source_file_name'); $table->char('source_sha256', 64); $table->dateTime('imported_at'); $table->timestamps(); $table->unique(['balance_date', 'store_name']); }); } }