* fix(vat): make the ruta drill-down reconcile with the figure it explains get_vat_declaration_totals drops four classes of entry before summing: posted closing entries, source_type 'vat_settlement', the two kontantmetod year-end reversals, and anything shaped like a momsredovisning. The drill-down behind each ruta filtered on company, status and date only. So expanding a ruta listed verifikat that are not in the number it claims to explain, and the panel shows no total that would reveal the mismatch. On production, 322 posted/reversed entries carrying 26xx lines across 214 companies sit in those excluded classes. A momsdeklaration is räkenskapsinformation under BFL 5 kap. and this drill-down is what a consultant uses to substantiate a filed figure, so the two have to agree exactly. The exclusion CTEs are lifted verbatim from the figure rather than re-derived, because any divergence reintroduces exactly this bug. The new pg test asserts the equality for the whole account set at once, so editing one function and not the other fails CI instead of silently misreporting. opening_balance entries are deliberately kept: the figure exempts them from its `shaped` set, which leaves their lines in the totals, so excluding them here would break the equality in the other direction. That has its own test. Verified the test catches the defect by reinstalling the old function body and watching it fail with the real numbers (2611: drill-down 250/240 vs figure 0/200), then restoring. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(vat): update the existing drill-down pg test to the new signature get_vat_ruta_source_lines gained p_ruta_accounts / p_net_accounts, and production-error-regressions.pg.test.ts still called the old 9-argument form, so pg-real failed with 42883 "function does not exist". I had grepped app/, lib/ and extensions/ for callers and not tests/. Neither fixture in that paging test is settlement-shaped, so paging behaviour is unchanged; the equality itself is covered by the new reconcile test. Also documents, in the tool-pg reset script, that its blanket grant to `anon` (which PostgREST requires) makes that database invalid for the pg-real suite: ~29 of those files assert least privilege and fail there even on unmodified main. That cost a confusing local run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
184 lines
5.6 KiB
TypeScript
184 lines
5.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { getPool } from './setup'
|
|
import {
|
|
insertAuthUser,
|
|
insertCompany,
|
|
insertFiscalPeriod,
|
|
insertPostedJournalEntry,
|
|
} from './fixtures'
|
|
|
|
async function insertEntry(params: {
|
|
userId: string
|
|
companyId: string
|
|
fiscalPeriodId: string
|
|
voucherNumber: number
|
|
entryDate: string
|
|
lines: Array<{ account: string; debit: number; credit: number }>
|
|
}): Promise<string> {
|
|
return insertPostedJournalEntry({
|
|
userId: params.userId,
|
|
companyId: params.companyId,
|
|
fiscalPeriodId: params.fiscalPeriodId,
|
|
voucherNumber: params.voucherNumber,
|
|
entryDate: params.entryDate,
|
|
description: 'Production regression test',
|
|
sourceType: 'manual',
|
|
lines: params.lines.map((line) => ({
|
|
accountNumber: line.account,
|
|
debitAmount: line.debit,
|
|
creditAmount: line.credit,
|
|
})),
|
|
})
|
|
}
|
|
|
|
async function seedCompany() {
|
|
const userId = await insertAuthUser()
|
|
const companyId = await insertCompany({ createdBy: userId })
|
|
const fiscalPeriodId = await insertFiscalPeriod({
|
|
userId,
|
|
companyId,
|
|
periodStart: '2026-01-01',
|
|
periodEnd: '2026-12-31',
|
|
})
|
|
return { userId, companyId, fiscalPeriodId }
|
|
}
|
|
|
|
describe('production error regressions', () => {
|
|
it('registers PendingOperationApproved in the processing history catalog', async () => {
|
|
const { rows } = await getPool().query(
|
|
`SELECT event_type
|
|
FROM public.processing_event_types
|
|
WHERE event_type = 'PendingOperationApproved'`,
|
|
)
|
|
|
|
expect(rows).toEqual([{ event_type: 'PendingOperationApproved' }])
|
|
})
|
|
|
|
it('registers the WhatsApp channel question events in the processing history catalog', async () => {
|
|
// appendQuestionHistory() swallows FK failures so the reply to the sender
|
|
// still goes out, so an unregistered type is invisible outside the logs.
|
|
const { rows } = await getPool().query(
|
|
`SELECT event_type
|
|
FROM public.processing_event_types
|
|
WHERE event_type = ANY($1::text[])
|
|
ORDER BY event_type`,
|
|
[['ChannelQuestionAsked', 'ChannelQuestionAnswered', 'ChannelQuestionExpired']],
|
|
)
|
|
|
|
expect(rows.map((row) => row.event_type)).toEqual([
|
|
'ChannelQuestionAnswered',
|
|
'ChannelQuestionAsked',
|
|
'ChannelQuestionExpired',
|
|
])
|
|
})
|
|
|
|
it('aggregates period activity and excludes a specified opening entry', async () => {
|
|
const ctx = await seedCompany()
|
|
const openingId = await insertEntry({
|
|
...ctx,
|
|
voucherNumber: 1,
|
|
entryDate: '2026-01-01',
|
|
lines: [
|
|
{ account: '1930', debit: 1_000, credit: 0 },
|
|
{ account: '2010', debit: 0, credit: 1_000 },
|
|
],
|
|
})
|
|
await insertEntry({
|
|
...ctx,
|
|
voucherNumber: 2,
|
|
entryDate: '2026-03-15',
|
|
lines: [
|
|
{ account: '1930', debit: 250, credit: 0 },
|
|
{ account: '3001', debit: 0, credit: 250 },
|
|
],
|
|
})
|
|
|
|
const { rows } = await getPool().query(
|
|
`SELECT account_number, debit::text, credit::text
|
|
FROM public.get_account_period_activity($1, $2, $3, $4, $5)`,
|
|
[ctx.companyId, '2026-01-01', '2026-12-31', ['1930', '3001'], openingId],
|
|
)
|
|
|
|
expect(rows).toEqual([
|
|
{ account_number: '1930', debit: '250', credit: '0' },
|
|
{ account_number: '3001', debit: '0', credit: '250' },
|
|
])
|
|
})
|
|
|
|
it('pages VAT source lines with a stable entry and line cursor', async () => {
|
|
const ctx = await seedCompany()
|
|
await insertEntry({
|
|
...ctx,
|
|
voucherNumber: 1,
|
|
entryDate: '2026-03-01',
|
|
lines: [
|
|
{ account: '1930', debit: 125, credit: 0 },
|
|
{ account: '2611', debit: 0, credit: 25 },
|
|
{ account: '3001', debit: 0, credit: 100 },
|
|
],
|
|
})
|
|
await insertEntry({
|
|
...ctx,
|
|
voucherNumber: 2,
|
|
entryDate: '2026-03-02',
|
|
lines: [
|
|
{ account: '1930', debit: 250, credit: 0 },
|
|
{ account: '2611', debit: 0, credit: 50 },
|
|
{ account: '3001', debit: 0, credit: 200 },
|
|
],
|
|
})
|
|
|
|
// p_ruta_accounts / p_net_accounts are the settlement-shape detectors the
|
|
// drill-down gained in 20260828172003 so it drops the same entries as the
|
|
// filed figure. Neither fixture here is settlement-shaped, so paging is
|
|
// unaffected; the equality itself is covered by
|
|
// tests/pg/vat-ruta-drilldown-reconcile.pg.test.ts.
|
|
const first = await getPool().query(
|
|
`SELECT * FROM public.get_vat_ruta_source_lines(
|
|
$1, $2, $3, $4, $5, $6, NULL, NULL, NULL, NULL, 1
|
|
)`,
|
|
[ctx.companyId, '2026-03-01', '2026-03-31', ['2611'], ['2611'], ['2650', '1650']],
|
|
)
|
|
expect(first.rows).toHaveLength(1)
|
|
expect(first.rows[0].voucher_number).toBe(1)
|
|
|
|
const second = await getPool().query(
|
|
`SELECT * FROM public.get_vat_ruta_source_lines(
|
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 1
|
|
)`,
|
|
[
|
|
ctx.companyId,
|
|
'2026-03-01',
|
|
'2026-03-31',
|
|
['2611'],
|
|
['2611'],
|
|
['2650', '1650'],
|
|
first.rows[0].entry_date,
|
|
first.rows[0].voucher_number,
|
|
first.rows[0].journal_entry_id,
|
|
first.rows[0].line_id,
|
|
],
|
|
)
|
|
expect(second.rows).toHaveLength(1)
|
|
expect(second.rows[0].voucher_number).toBe(2)
|
|
})
|
|
|
|
it('installs the covering indexes used by the timeout fixes', async () => {
|
|
const { rows } = await getPool().query(
|
|
`SELECT indexname
|
|
FROM pg_indexes
|
|
WHERE schemaname = 'public'
|
|
AND indexname IN (
|
|
'idx_audit_log_company_created_id',
|
|
'idx_journal_entries_company_posted_date_id'
|
|
)
|
|
ORDER BY indexname`,
|
|
)
|
|
|
|
expect(rows.map((row) => row.indexname)).toEqual([
|
|
'idx_audit_log_company_created_id',
|
|
'idx_journal_entries_company_posted_date_id',
|
|
])
|
|
})
|
|
})
|