[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', ]); DB::purge('business_mirror'); $this->createTables(); } public function test_account_bill_page_reads_authoritative_rows_with_filters_and_totals(): void { $this->insertSucceededImport(); $this->insertRow('REC-INCOME', '拼多多一店', '交易收款', '2026-08-01 00:00:00', '100.00', '0.00', '100.00', 'M-100', 'O-100'); $this->insertRow('REC-EXPENSE', '拼多多一店', '退款', '2026-08-31 23:59:59', '0.00', '20.00', '-20.00', 'M-200', 'O-200'); $this->insertRow('REC-OTHER', '拼多多二店', '交易收款', '2026-08-15 12:00:00', '50.00', '0.00', '50.00', 'M-300', 'O-300'); $result = app(PinduoduoSettlementBillRepository::class)->paginateAccountBills([ 'store_name' => '拼多多一店', 'start' => '2026-08-01', 'end' => '2026-08-31', ], 'occurred_at', 'desc', 1, 10); $this->assertSame(2, $result['total']); $this->assertSame(100.0, $result['summary']['income']); $this->assertSame(20.0, $result['summary']['expense']); $this->assertSame(0.0, $result['summary']['opening_balance']); $this->assertSame(80.0, $result['summary']['balance']); $this->assertSame('REC-EXPENSE', $result['data'][0]['record_code']); $this->assertSame('M-200', $result['data'][0]['order_no']); $this->assertSame('O-200', $result['data'][0]['payload']['online_order_no']); $this->assertSame('拼多多货款账户', $result['data'][0]['payload']['type']); $this->assertSame(20.0, $result['data'][0]['amount']); $this->assertSame(-20.0, $result['data'][0]['balance']); $filtered = app(PinduoduoSettlementBillRepository::class)->paginateAccountBills([ 'bill_item' => '退款', 'keyword' => 'O-200', ], 'record_code', 'asc', 1, 10); $this->assertSame(1, $filtered['total']); $this->assertSame('REC-EXPENSE', $filtered['data'][0]['record_code']); $options = app(PinduoduoSettlementBillRepository::class)->accountBillFilterOptions(); $this->assertSame(['拼多多一店', '拼多多二店'], $options['shops']); $this->assertSame(['交易收款', '退款'], $options['bill_items']); $export = iterator_to_array(app(PinduoduoSettlementBillRepository::class)->exportAccountBills([ 'bill_item' => '退款', ], 'record_code', 'asc')); $this->assertCount(1, $export); $this->assertSame('REC-EXPENSE', $export[0]['record_code']); } public function test_platform_balance_rolls_forward_from_june_into_later_months(): void { $this->insertSucceededImport(); $this->insertBalanceSnapshot('拼多多-一店', '100.00'); $this->insertBalanceSnapshot('拼多多-二店', '50.00'); $this->insertBalanceSnapshot('拼多多-平台独有店', '20.00'); $this->insertRow('JUL-A-IN', '拼多多-一店', '交易收款', '2026-07-10 10:00:00', '30.00', '0.00', '30.00', 'M-1', 'O-1'); $this->insertRow('JUL-A-OUT', '拼多多-一店', '退款', '2026-07-11 10:00:00', '0.00', '5.00', '-5.00', 'M-2', 'O-2'); $this->insertRow('JUL-B-IN', '拼多多-二店', '交易收款', '2026-07-12 10:00:00', '8.00', '0.00', '8.00', 'M-3', 'O-3'); $this->insertRow('AUG-A-IN', '拼多多-一店', '交易收款', '2026-08-10 10:00:00', '10.00', '0.00', '10.00', 'M-4', 'O-4'); $repository = app(PinduoduoSettlementBillRepository::class); $july = $repository->paginateAccountBills([ 'start' => '2026-07-01', 'end' => '2026-07-31', ], 'occurred_at', 'desc', 1, 10); $august = $repository->paginateAccountBills([ 'start' => '2026-08-01', 'end' => '2026-08-31', ], 'occurred_at', 'desc', 1, 10); $this->assertSame(170.0, $july['summary']['opening_balance']); $this->assertSame('2026-06-30', $july['summary']['opening_balance_date']); $this->assertSame('platform_snapshot', $july['summary']['opening_balance_source']); $this->assertSame(38.0, $july['summary']['income']); $this->assertSame(5.0, $july['summary']['expense']); $this->assertSame(203.0, $july['summary']['balance']); $this->assertSame($july['summary']['balance'], $august['summary']['opening_balance']); $this->assertSame('2026-07-31', $august['summary']['opening_balance_date']); $this->assertSame('previous_period', $august['summary']['opening_balance_source']); $this->assertSame(213.0, $august['summary']['balance']); $oneStore = $repository->paginateAccountBills([ 'store_name' => '拼多多-一店', 'start' => '2026-08-01', 'end' => '2026-08-31', ], 'occurred_at', 'desc', 1, 10); $this->assertSame(125.0, $oneStore['summary']['opening_balance']); $this->assertSame(135.0, $oneStore['summary']['balance']); } public function test_account_bill_page_fails_closed_until_a_successful_import_exists(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('pinduoduo_authoritative_bills_not_ready'); app(PinduoduoSettlementBillRepository::class)->paginateAccountBills( [], 'occurred_at', 'desc', 1, 10, ); } private function createTables(): void { $schema = Schema::connection('business_mirror'); $schema->create('pdd_settlement_bill_imports', function (Blueprint $table): void { $table->uuid('generation')->primary(); $table->string('status', 32); $table->timestamp('updated_at')->nullable(); }); $schema->create('pdd_settlement_bill_rows', function (Blueprint $table): void { $table->string('record_code', 191)->primary(); $table->string('store_name')->nullable(); $table->string('custom_remark')->nullable(); $table->string('bill_item')->nullable(); $table->string('merchant_order_no', 191)->nullable(); $table->string('online_order_no', 191)->nullable(); $table->string('business_type')->nullable(); $table->string('business_description', 1000)->nullable(); $table->string('account_name')->nullable(); $table->dateTime('occurred_at'); $table->decimal('income_amount', 20, 2); $table->decimal('expense_amount', 20, 2); $table->decimal('signed_amount', 20, 2); $table->string('remark', 1000)->nullable(); $table->dateTime('source_updated_at')->nullable(); $table->string('source_modified_by')->nullable(); }); $schema->create('pdd_platform_balance_snapshots', function (Blueprint $table): void { $table->bigIncrements('id'); $table->date('period_end'); $table->string('store_name'); $table->string('normalized_store_name'); $table->string('account_name')->nullable(); $table->decimal('balance', 20, 6); $table->string('source_filename'); $table->char('source_sha256', 64); $table->dateTime('imported_at'); }); } private function insertSucceededImport(): void { DB::connection('business_mirror')->table('pdd_settlement_bill_imports')->insert([ 'generation' => '11111111-1111-1111-1111-111111111111', 'status' => 'succeeded', ]); } private function insertBalanceSnapshot(string $storeName, string $balance): void { DB::connection('business_mirror')->table('pdd_platform_balance_snapshots')->insert([ 'period_end' => '2026-06-30', 'store_name' => $storeName, 'normalized_store_name' => JushuitanShopBalanceRepository::normalizeStoreName($storeName), 'account_name' => '拼多多货款账户', 'balance' => $balance, 'source_filename' => '拼多多6月平台期末余额.xlsx', 'source_sha256' => str_repeat('a', 64), 'imported_at' => '2026-11-06 10:00:00', ]); } private function insertRow( string $recordCode, string $storeName, string $billItem, string $occurredAt, string $income, string $expense, string $signed, string $merchantOrderNo, string $onlineOrderNo, ): void { DB::connection('business_mirror')->table('pdd_settlement_bill_rows')->insert([ 'record_code' => $recordCode, 'store_name' => $storeName, 'custom_remark' => '自定义备注', 'bill_item' => $billItem, 'merchant_order_no' => $merchantOrderNo, 'online_order_no' => $onlineOrderNo, 'business_type' => '货款', 'business_description' => '业务描述', 'account_name' => '拼多多货款账户', 'occurred_at' => $occurredAt, 'income_amount' => $income, 'expense_amount' => $expense, 'signed_amount' => $signed, 'remark' => '账单备注', 'source_updated_at' => '2026-09-01 08:00:00', 'source_modified_by' => '聚胜算', ]); } }