table('fin_accounts')->insert([ 'company_name' => '测试网商公司', 'account_short' => '测试网商银行', 'account_no' => '8888000012345678', 'account_type' => '网商银行', 'current_balance' => 1000, ]); $connection->table('fin_account_opening')->insert([ 'account_short' => '测试网商银行', 'opening_date' => '2026-10-01', 'opening_balance' => 1000, ]); $connection->table('fin_transactions')->insert([ 'txn_date' => '2026-11-01', 'account_short' => '测试网商银行', 'detail' => '旧版本', 'income' => 999, 'expense' => 0, 'source' => WangshangBankBillImporter::SOURCE, 'source_value' => '8888000012345678', 'process_type' => WangshangBankBillImporter::PROCESS_TYPE, 'row_key' => hash('sha256', 'old-wangshang-row'), ]); $bundle = $this->bundle([ ['交易时间', '账务流水号', '交易名称', '对方户名', '借方金额(收)', '贷方金额(支)', '交易状态'], ['2026-11-01 09:10:00', '202611010001', '他行转入', '甲公司', '100.00', '-', '成功'], ['2026-11-01 10:20:00', '202611010002', '转账汇款', '乙公司', '-', '20.00', '成功'], ['2026-10-31 10:20:00', '202610310001', '非目标日期', '丙公司', '999.00', '-', '成功'], ], expectedIncome: 100, expectedExpense: 20); $this->artisan('finance-ledger:import-wangshang-bank', [ 'path' => $bundle, '--date' => '2026-11-01', ])->assertExitCode(0); $this->assertDatabaseCount('fin_transactions', 2, 'business_mirror'); $this->assertDatabaseHas('fin_transactions', [ 'account_short' => '测试网商银行', 'approval_no' => '202611010001', 'income' => 100, 'expense' => 0, 'source' => WangshangBankBillImporter::SOURCE, 'source_value' => '8888000012345678', ], 'business_mirror'); $this->assertDatabaseHas('fin_transactions', [ 'approval_no' => '202611010002', 'income' => 0, 'expense' => 20, ], 'business_mirror'); $this->assertDatabaseMissing('fin_transactions', ['detail' => '旧版本'], 'business_mirror'); $this->assertDatabaseHas('fin_account_map', [ 'source' => WangshangBankBillImporter::SOURCE, 'source_value' => '8888000012345678', 'account_short' => '测试网商银行', 'txn_count' => 2, 'income_sum' => 100, 'expense_sum' => 20, ], 'business_mirror'); $this->assertDatabaseHas('fin_daily_flows', [ 'flow_date' => '2026-11-01', 'account_short' => '测试网商银行', 'opening_balance' => 1000, 'inflow' => 100, 'outflow' => 20, 'closing_balance' => 1080, ], 'business_mirror'); } public function test_row_count_mismatch_keeps_existing_transactions(): void { $connection = DB::connection('business_mirror'); $connection->table('fin_accounts')->insert([ 'company_name' => '测试网商公司', 'account_short' => '测试网商银行', 'account_no' => '8888000012345678', 'account_type' => '网商银行', ]); $connection->table('fin_transactions')->insert([ 'txn_date' => '2026-11-01', 'account_short' => '测试网商银行', 'detail' => '应保留', 'income' => 1, 'expense' => 0, 'source' => WangshangBankBillImporter::SOURCE, 'source_value' => '8888000012345678', 'process_type' => WangshangBankBillImporter::PROCESS_TYPE, 'row_key' => hash('sha256', 'keep-wangshang-row'), ]); $bundle = $this->bundle([ ['交易时间', '账务流水号', '交易名称', '借方金额(收)', '贷方金额(支)'], ['2026-11-01 09:10:00', '202611010001', '他行转入', '100.00', '-'], ], expectedRows: 2); $this->withExceptionHandling()->artisan('finance-ledger:import-wangshang-bank', [ 'path' => $bundle, '--date' => '2026-11-01', ])->assertExitCode(1); $this->assertDatabaseHas('fin_transactions', ['detail' => '应保留'], 'business_mirror'); } public function test_imports_transactions_when_the_account_master_has_not_been_extended(): void { $bundle = $this->bundle([ ['交易时间', '账务流水号', '交易名称', '借方金额(收)', '贷方金额(支)'], ['2026-11-01 09:10:00', '202611010003', '他行转入', '100.00', '-'], ], expectedRows: 1, expectedIncome: 100, expectedExpense: 0); $this->artisan('finance-ledger:import-wangshang-bank', [ 'path' => $bundle, '--date' => '2026-11-01', '--dry-run' => true, ])->assertExitCode(0); $this->assertDatabaseCount('fin_transactions', 0, 'business_mirror'); $this->artisan('finance-ledger:import-wangshang-bank', [ 'path' => $bundle, '--date' => '2026-11-01', ])->assertExitCode(0); $this->assertDatabaseHas('fin_transactions', [ 'account_short' => '测试网商银行', 'approval_no' => '202611010003', 'income' => 100, ], 'business_mirror'); $this->assertDatabaseHas('fin_account_map', [ 'source' => WangshangBankBillImporter::SOURCE, 'source_value' => '8888000012345678', 'account_short' => '测试网商银行', ], 'business_mirror'); } /** * @param list> $rows */ private function bundle( array $rows, int $expectedRows = 2, ?float $expectedIncome = null, ?float $expectedExpense = null, ): string { $directory = sys_get_temp_dir() . '/wangshang-test-' . bin2hex(random_bytes(6)); mkdir($directory, 0700, true); $csv = $directory . '/bills.csv'; $stream = fopen($csv, 'wb'); foreach ($rows as $row) { fputcsv($stream, $row); } fclose($stream); file_put_contents($directory . '/manifest.json', json_encode([ 'date' => '2026-11-01', 'accounts' => [[ 'company' => '测试网商公司', 'account' => '8888000012345678', 'account_short' => '测试网商银行', 'expected_rows' => $expectedRows, 'expected_income' => $expectedIncome, 'expected_expense' => $expectedExpense, 'file' => 'bills.csv', ]], ], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)); $zipPath = sys_get_temp_dir() . '/wangshang-test-' . bin2hex(random_bytes(6)) . '.zip'; $zip = new ZipArchive(); $this->assertTrue($zip->open($zipPath, ZipArchive::CREATE) === true); $zip->addFile($directory . '/manifest.json', 'manifest.json'); $zip->addFile($csv, 'bills.csv'); $zip->close(); unlink($csv); unlink($directory . '/manifest.json'); rmdir($directory); return $zipPath; } }