fix(export): paginate the archive size estimate and explain scope counts (#1635)

The period branch of estimateArchiveSize ran a single unpaginated
document read with one flat IN() over every posted entry id in the
year: past the PostgREST row cap it silently undercounts, and past a
few hundred entry ids the URL itself blows up. Chunk the id filter
(CHILD_FK_CHUNK) and paginate every read with fetchAllRows, mirroring
what writeDocuments already did (the ZIP content was never affected).

The dialog now says per scope which documents are counted: full
history includes unlinked inbox/receipt documents, a single year only
those linked to posted vouchers. Without that line, a company with
many unlinked receipts reads the count gap as a pagination bug.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-17 10:20:18 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 62c6fc44fe
commit 2eb3441244
6 changed files with 83 additions and 20 deletions
@@ -1037,4 +1037,34 @@ describe('estimateArchiveSize', () => {
expect(result.document_count).toBe(0)
expect(result.total_bytes).toBe(8 * 1024 * 1024)
})
it('paginates the all-mode document read past the page cap', async () => {
const firstPage = Array.from({ length: 1000 }, () => ({ file_size_bytes: 1_000 }))
enqueueMany([
{ data: firstPage }, // full first page forces a second fetch
{ data: [{ file_size_bytes: 1_000 }] },
])
const result = await estimateArchiveSize(supabase as any, 'company-1', 'all')
expect(result.document_count).toBe(1001)
expect(result.document_bytes).toBe(1_001_000)
})
it('chunks the period-mode entry-id filter and sums across chunks', async () => {
// 250 posted entries -> three IN() chunks of max 100 ids. An unchunked
// implementation consumes a single document response and undercounts.
const entryIds = Array.from({ length: 250 }, (_, i) => ({ id: `e${i}` }))
enqueueMany([
{ data: entryIds }, // journal_entries for periodEntryIds
{ data: [{ file_size_bytes: 100 }] }, // chunk 1
{ data: [{ file_size_bytes: 200 }] }, // chunk 2
{ data: [{ file_size_bytes: 300 }] }, // chunk 3
])
const result = await estimateArchiveSize(supabase as any, 'company-1', 'period', 'p-1')
expect(result.document_count).toBe(3)
expect(result.document_bytes).toBe(600)
})
})