files as $path) { @unlink($path); } foreach ($this->directories as $directory) { @rmdir($directory); } parent::tearDown(); } public function test_it_imports_exported_details_with_store_mapping_and_replaces_only_the_period(): void { $companyId = (int) DB::table('companies')->value('id'); $this->existingBill('OLD-TARGET', '唯品会MP-亿家达家具旗舰店', '2026-10'); $this->existingBill('KEEP-PERIOD', '唯品会MP-亿家达家具旗舰店', '2026-09'); $this->existingBill('KEEP-STORE', '唯品会MP-卓禾家具特卖旗舰店', '2026-10'); $first = $this->xlsx( 'vip-mp-yijiada_MP-ST96730107-20261101-01.xlsx', [$this->row('REC-INCOME', '亿家达家具特卖旗舰店', '100', '10', '90')], ); $second = $this->xlsx( 'vip-mp-pumei_MP-ST86337204-20261101-01.xlsx', [$this->row('REC-EXPENSE', '朴美家具特卖旗舰店', '-50', '-5', '-45')], ); $result = app(VipshopMpBillImporter::class)->import( [$first, $second], '2026-10', [ '亿家达家具特卖旗舰店' => '唯品会MP-亿家达家具旗舰店', '朴美家具特卖旗舰店' => '唯品会MP-朴美家具特卖旗舰店', ], $companyId, ); $this->assertSame('succeeded', $result['status']); $this->assertSame(2, $result['row_count']); $this->assertSame(2, $result['store_count']); $this->assertSame(2, $result['bill_count']); $this->assertSame('110.000000', $result['income_total']); $this->assertSame('55.000000', $result['expense_total']); $this->assertSame('45.000000', $result['balance_total']); $this->assertDatabaseMissing('ec_account_bills', ['record_code' => 'OLD-TARGET']); $this->assertDatabaseHas('ec_account_bills', ['record_code' => 'KEEP-PERIOD']); $this->assertDatabaseHas('ec_account_bills', ['record_code' => 'KEEP-STORE']); $row = AccountBill::query()->where('record_code', 'REC-EXPENSE')->firstOrFail(); $this->assertSame('唯品会MP-朴美家具特卖旗舰店', $row->store_name); $this->assertSame('2026-10', $row->payload['settle_period']); $this->assertSame('vip-mp-pumei_MP-ST86337204-20261101-01.xlsx', $row->payload['source_file']); $this->assertSame(-55.0, (float) $row->amount); $this->assertSame(55.0, (float) $row->expense); $again = app(VipshopMpBillImporter::class)->import( [$first, $second], '2026-10', [ '亿家达家具特卖旗舰店' => '唯品会MP-亿家达家具旗舰店', '朴美家具特卖旗舰店' => '唯品会MP-朴美家具特卖旗舰店', ], $companyId, ); $this->assertSame('succeeded', $again['status']); $this->assertSame(2, AccountBill::query()->whereIn('record_code', ['REC-INCOME', 'REC-EXPENSE'])->count()); } public function test_duplicate_record_codes_fail_without_changing_existing_period(): void { $companyId = (int) DB::table('companies')->value('id'); $this->existingBill('ACTIVE', '唯品会MP-亿家达家具旗舰店', '2026-10'); $first = $this->xlsx( 'vip-mp-one_MP-ST96730107-20261101-01.xlsx', [$this->row('DUPLICATE', '亿家达家具特卖旗舰店', '10', '0', '10')], ); $second = $this->xlsx( 'vip-mp-two_MP-ST86337204-20261101-01.xlsx', [$this->row('DUPLICATE', '朴美家具特卖旗舰店', '20', '0', '20')], ); try { app(VipshopMpBillImporter::class)->import( [$first, $second], '2026-10', [ '亿家达家具特卖旗舰店' => '唯品会MP-亿家达家具旗舰店', '朴美家具特卖旗舰店' => '唯品会MP-朴美家具特卖旗舰店', ], $companyId, ); $this->fail('重复全局ID必须拒绝导入。'); } catch (InvalidArgumentException $exception) { $this->assertStringContainsString('重复', $exception->getMessage()); } $this->assertDatabaseHas('ec_account_bills', ['record_code' => 'ACTIVE']); $this->assertDatabaseMissing('ec_account_bills', ['record_code' => 'DUPLICATE']); } private function row( string $recordCode, string $storeName, string $total, string $technicalFee, string $receivable, ): array { return [ $recordCode, 'ST00000001', $storeName, '2026-10-15 10:00:00', '销售订单', '货款', '', '', '26010100000001', 'SKU-1', 'ITEM-1', 'SKU-1', '', 'AGREEMENT-1', '', '书柜', '100', '1', $total, '0', '0', '0', $total, '10.00%', $technicalFee, '0', '0.00%', '0', $receivable, ]; } private function xlsx(string $name, array $rows): string { $spreadsheet = new Spreadsheet(); $spreadsheet->getActiveSheet()->setTitle('订单明细'); $spreadsheet->getActiveSheet()->fromArray(array_merge([self::HEADERS], $rows)); $directory = sys_get_temp_dir() . '/vipshop-mp-' . uniqid('', true); mkdir($directory, 0700, true); $this->directories[] = $directory; $path = $directory . '/' . $name; (new Xlsx($spreadsheet))->save($path); $spreadsheet->disconnectWorksheets(); $this->files[] = $path; return $path; } private function existingBill(string $recordCode, string $storeName, string $period): void { DB::table('ec_account_bills')->insert([ 'company_id' => (int) DB::table('companies')->value('id'), 'bill_type' => 'vipshop', 'record_code' => $recordCode, 'store_name' => $storeName, 'order_no' => 'OLD', 'bill_item' => '原项目', 'occurred_at' => '2026-10-15 00:00:00', 'income' => 1, 'expense' => 0, 'balance' => 0, 'payload' => json_encode(['settle_period' => $period], JSON_THROW_ON_ERROR), 'created_at' => now(), 'updated_at' => now(), ]); } }