fix: prevent silent data truncation in report generators (#257)
* fix: prevent silent data truncation in report generators Supabase PostgREST silently truncates queries at 1000 rows. Several report generators used bare .select() without fetchAllRows(), causing incomplete financial data — a BFL compliance violation. Wrapped 10 queries across 7 files with fetchAllRows(): - monthly-breakdown: journal_entry_lines (easily >1000/period) - ar-ledger: unpaid invoices - supplier-ledger: unpaid supplier invoices - salary-journal: salary_run_employees (+ optimized with !inner join) - vacation-liability: employees + salary_run_employees - full-archive-export: document_attachments + journal_entry IDs - ingest.ts: supplier invoices for auto-matching Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Greptile review — try/catch and client-side safety checks - Wrap full-archive-export document fetch in try/catch to match the VAT section pattern — a failed document query should not prevent the rest of the archive from being generated. - Restore client-side year/status safety checks in salary-journal and vacation-liability as defense-in-depth against PostgREST !inner filter regressions, per BFL lönejournal and BFNAR 2016:10 compliance. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4fbfadb2b7
commit
fe137346df
@@ -9,7 +9,7 @@ let results: Array<{ data?: unknown; error?: unknown }>
|
||||
|
||||
function makeBuilder() {
|
||||
const b: Record<string, unknown> = {}
|
||||
for (const m of ['select', 'eq', 'in']) {
|
||||
for (const m of ['select', 'eq', 'in', 'range']) {
|
||||
b[m] = vi.fn().mockReturnValue(b)
|
||||
}
|
||||
b.single = vi.fn().mockImplementation(async () => results[resultIdx++] ?? { data: null, error: null })
|
||||
|
||||
@@ -54,11 +54,13 @@ describe('generateMonthlyBreakdown', () => {
|
||||
select: () => ({
|
||||
eq: () => ({
|
||||
eq: () => ({
|
||||
eq: () =>
|
||||
Promise.resolve({
|
||||
data: [],
|
||||
error: null,
|
||||
}),
|
||||
eq: () => ({
|
||||
range: () =>
|
||||
Promise.resolve({
|
||||
data: [],
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
@@ -96,36 +98,38 @@ describe('generateMonthlyBreakdown', () => {
|
||||
select: () => ({
|
||||
eq: () => ({
|
||||
eq: () => ({
|
||||
eq: () =>
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
account_number: '3001',
|
||||
debit_amount: 0,
|
||||
credit_amount: 10000,
|
||||
journal_entry: { entry_date: '2024-01-15', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '5010',
|
||||
debit_amount: 3000,
|
||||
credit_amount: 0,
|
||||
journal_entry: { entry_date: '2024-01-20', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '3001',
|
||||
debit_amount: 0,
|
||||
credit_amount: 5000,
|
||||
journal_entry: { entry_date: '2024-02-10', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '6200',
|
||||
debit_amount: 1500,
|
||||
credit_amount: 0,
|
||||
journal_entry: { entry_date: '2024-02-15', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
],
|
||||
error: null,
|
||||
}),
|
||||
eq: () => ({
|
||||
range: () =>
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
account_number: '3001',
|
||||
debit_amount: 0,
|
||||
credit_amount: 10000,
|
||||
journal_entry: { entry_date: '2024-01-15', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '5010',
|
||||
debit_amount: 3000,
|
||||
credit_amount: 0,
|
||||
journal_entry: { entry_date: '2024-01-20', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '3001',
|
||||
debit_amount: 0,
|
||||
credit_amount: 5000,
|
||||
journal_entry: { entry_date: '2024-02-10', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '6200',
|
||||
debit_amount: 1500,
|
||||
credit_amount: 0,
|
||||
journal_entry: { entry_date: '2024-02-15', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
],
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
@@ -175,36 +179,38 @@ describe('generateMonthlyBreakdown', () => {
|
||||
select: () => ({
|
||||
eq: () => ({
|
||||
eq: () => ({
|
||||
eq: () =>
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
account_number: '1930',
|
||||
debit_amount: 10000,
|
||||
credit_amount: 0,
|
||||
journal_entry: { entry_date: '2024-01-15', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '2611',
|
||||
debit_amount: 0,
|
||||
credit_amount: 2500,
|
||||
journal_entry: { entry_date: '2024-01-15', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '8400',
|
||||
debit_amount: 500,
|
||||
credit_amount: 0,
|
||||
journal_entry: { entry_date: '2024-01-20', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '8300',
|
||||
debit_amount: 0,
|
||||
credit_amount: 200,
|
||||
journal_entry: { entry_date: '2024-01-25', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
],
|
||||
error: null,
|
||||
}),
|
||||
eq: () => ({
|
||||
range: () =>
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
account_number: '1930',
|
||||
debit_amount: 10000,
|
||||
credit_amount: 0,
|
||||
journal_entry: { entry_date: '2024-01-15', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '2611',
|
||||
debit_amount: 0,
|
||||
credit_amount: 2500,
|
||||
journal_entry: { entry_date: '2024-01-15', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '8400',
|
||||
debit_amount: 500,
|
||||
credit_amount: 0,
|
||||
journal_entry: { entry_date: '2024-01-20', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
{
|
||||
account_number: '8300',
|
||||
debit_amount: 0,
|
||||
credit_amount: 200,
|
||||
journal_entry: { entry_date: '2024-01-25', status: 'posted', user_id: 'user-1', fiscal_period_id: 'period-1' },
|
||||
},
|
||||
],
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -9,7 +9,7 @@ let results: Array<{ data?: unknown; error?: unknown }>
|
||||
|
||||
function makeBuilder() {
|
||||
const b: Record<string, unknown> = {}
|
||||
for (const m of ['select', 'eq', 'in']) {
|
||||
for (const m of ['select', 'eq', 'in', 'range']) {
|
||||
b[m] = vi.fn().mockReturnValue(b)
|
||||
}
|
||||
b.single = vi.fn().mockImplementation(async () => results[resultIdx++] ?? { data: null, error: null })
|
||||
|
||||
Reference in New Issue
Block a user