diff --git a/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php b/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php index b9ae8095..5b7952c1 100644 --- a/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php @@ -40,7 +40,14 @@ class StoreReceivingAlipayBillMirrorRepository $query = $this->billQuery(); $this->applyFilters($query, $filters); - $total = (clone $query)->count(); + $state = $this->mirror->state($dataset) ?? []; + // The synchronizer persists an exact COUNT in the same transaction that + // marks a run successful. Never substitute a database row estimate. + $hasFilters = collect($filters)->contains(static fn (mixed $value): bool => $value !== null && trim((string) $value) !== ''); + $recordedCount = $state['mirror_row_count'] ?? null; + $useRecordedCount = ! $hasFilters && ($state['status'] ?? null) === 'succeeded' + && $recordedCount !== null && ctype_digit((string) $recordedCount); + $total = $useRecordedCount ? (int) $recordedCount : (clone $query)->count(); $summary = $this->summary($query, $filters); $billRows = $query @@ -54,7 +61,6 @@ class StoreReceivingAlipayBillMirrorRepository $rows = collect($billRows) ->map(fn (object $row): array => $this->mapRow($row, $shopNames[(string) $row->shop_id] ?? null)) ->all(); - $state = $this->mirror->state($dataset) ?? []; $income = $summary['income']; $expense = $summary['expense']; diff --git a/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php index f5b6dfae..0b1c3355 100644 --- a/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php @@ -43,6 +43,7 @@ class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase $table->dateTime('initial_completed_at')->nullable(); $table->string('status')->nullable(); $table->dateTime('last_succeeded_at')->nullable(); + $table->unsignedBigInteger('mirror_row_count')->nullable(); }); $schema->create('ops_jst_original_bill_info', function (Blueprint $table): void { $table->integer('Id'); @@ -160,6 +161,38 @@ class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase $this->assertSame(['income' => 100.25, 'expense' => 20.10, 'balance' => 80.15, 'count' => 1], $result['summary']); } + public function test_unfiltered_pages_reuse_exact_completed_count_and_observe_the_next_sync(): void + { + $this->markReady(); + $db = DB::connection('business_mirror'); + $db->table('ops_jst_original_bill_info')->insert([ + $this->row(1, 7, '2026-08-01 09:00:00', '1', 10), + $this->row(2, 7, '2026-08-02 09:00:00', '-1', 5), + ]); + $state = $db->table('business_mirror_table_states')->where('dataset_key', StoreReceivingAlipayBillMirrorRepository::DATASET); + $state->update(['mirror_row_count' => 2]); + $db->enableQueryLog(); + $first = $this->repository->paginate([], 'occurred_at', 'desc', 1, 1); + $second = $this->repository->paginate(['store_name' => '', 'start' => null], 'occurred_at', 'desc', 2, 1); + $this->assertSame(2, $first['total']); + $this->assertSame(2, $second['total']); + $this->assertNotSame($first['data'][0]['id'], $second['data'][0]['id']); + $this->assertCount(0, array_filter($db->getQueryLog(), fn ($q) => str_contains(strtolower($q['query']), 'count(*)'))); + $db->disableQueryLog(); + + $db->table('ops_jst_original_bill_info')->insert($this->row(3, 7, '2026-08-03 09:00:00', '1', 4)); + $state->update(['mirror_row_count' => 3, 'last_succeeded_at' => '2026-09-08 09:00:00']); + $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); + $this->assertSame(1, $this->repository->paginate(['business_type' => '支出'], 'occurred_at', 'desc', 1, 1)['total']); + + $state->update(['status' => 'running', 'mirror_row_count' => 2]); + $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); + $state->update(['status' => 'failed']); + $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); + $state->update(['status' => 'succeeded', 'mirror_row_count' => null]); + $this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']); + } + private function markReady(): void { foreach ([StoreReceivingAlipayBillMirrorRepository::DATASET, StoreReceivingAlipayBillMirrorRepository::SHOP_DATASET] as $dataset) {