feat: implement skattekonto drift detection and alerting (#525)
* feat: implement skattekonto drift detection and alerting - Add skattekonto drift computation logic to compare Skatteverket's saldo with GL 1630 sum. - Implement alerting mechanism for significant drift changes, with throttling to prevent alert spamming. - Introduce database functions to sum GL 1630 entries and list unbooked skattekonto rows. feat: create own account transfer detection - Develop logic to detect transfers between a company's own cash accounts based on counterparty IBAN. - Implement tests to validate detection logic under various scenarios, including matching and non-matching IBANs. feat: establish cash accounts as a first-class entity - Create cash_accounts table to manage routable cash accounts, replacing ad-hoc JSONB structures. - Implement functions for listing, upserting, and managing cash accounts, including primary account designation. feat: enhance GL line reconciliation functionality - Modify get_unlinked_1930_lines RPC to accept any account number for reconciliation, improving flexibility for different currencies. - Update related functions to ensure compatibility with the new cash_accounts structure. feat: capture counterparty IBAN in transactions - Add counterparty_iban column to transactions table to facilitate intra-account transfer detection. - Create index for efficient lookups based on counterparty IBAN. * feat: Enhance cash account handling and reconciliation processes - Updated reconciliation routes to enforce cash account validation for all account numbers, including '1930'. - Improved error handling for unknown cash accounts in reconciliation status and unmatched entries routes. - Changed CashAccountSelector to use sessionStorage instead of localStorage for better data privacy. - Fixed mapping for employer payroll taxes to route to the correct account (2730 instead of 2731). - Added safety checks for company IDs in the guessCounterAccount function to prevent injection vulnerabilities. - Introduced atomic RPC for setting primary cash accounts to avoid intermediate states during updates. - Seeded default cash accounts for new companies to ensure reconciliation routes are accessible from day one. - Updated email notifications for drift detection to avoid exposing sensitive financial data. - Enhanced bank reconciliation logic to handle multi-currency transactions correctly. - Renamed and updated tests to reflect changes in the underlying RPCs and ensure accurate coverage. - Migrated existing cash account rules to correct mappings in compliance with Swedish accounting standards.
This commit is contained in:
@@ -44,6 +44,7 @@ const PERSISTED_EVENT_TYPES: CoreEventType[] = [
|
||||
'bank_connection.consent_granted',
|
||||
'bank_connection.account_selection_changed',
|
||||
'bank_connection.revoked',
|
||||
'bank_connection.cash_account_mirror_failed',
|
||||
]
|
||||
|
||||
// Excluded (with reasoning):
|
||||
|
||||
@@ -52,6 +52,20 @@ export type CoreEvent =
|
||||
| { type: 'bank_connection.consent_granted'; payload: { connectionId: string; bankName: string | null; accountCount: number; consentExpiresAt: string | null; userId: string; companyId: string } }
|
||||
| { type: 'bank_connection.account_selection_changed'; payload: { connectionId: string; bankName: string | null; previousStatus: string; newStatus: string; enabledCount: number; totalCount: number; userId: string; companyId: string } }
|
||||
| { type: 'bank_connection.revoked'; payload: { connectionId: string; bankName: string | null; userId: string; companyId: string } }
|
||||
// Emitted when the PSD2 callback fails to mirror a returned account into
|
||||
// cash_accounts. ASVS V16 / ISO 27001 A.8.15 — security-relevant failures
|
||||
// must land in a structured audit log (event_log, 30-day TTL) rather than
|
||||
// being lost to console.error.
|
||||
| { type: 'bank_connection.cash_account_mirror_failed'; payload: {
|
||||
connectionId: string
|
||||
bankName: string | null
|
||||
accountUid: string
|
||||
ledgerAccount: string
|
||||
currency: string
|
||||
reason: string
|
||||
userId: string
|
||||
companyId: string
|
||||
} }
|
||||
// Periods
|
||||
| { type: 'period.locked'; payload: { period: FiscalPeriod; userId: string; companyId: string } }
|
||||
| { type: 'period.unlocked'; payload: { period: FiscalPeriod; userId: string; companyId: string } }
|
||||
@@ -107,6 +121,18 @@ export type CoreEvent =
|
||||
| { type: 'skattekonto.balance.changed'; payload: { previousBalance: number; currentBalance: number; userId: string; companyId: string } }
|
||||
| { type: 'skattekonto.transaction.upcoming'; payload: { transaktionsdatum: string; forfallodatum: string; transaktionstext: string; beloppSkatteverket: number; userId: string; companyId: string } }
|
||||
| { type: 'skattekonto.connection.expired'; payload: { reason: 'REFRESH_EXHAUSTED' | 'SESSION_EXPIRED' | 'TOKEN_CORRUPTED'; userId: string; companyId: string } }
|
||||
// Fired when the SKV saldo and GL 1630 sum diverge beyond the configured
|
||||
// tolerance. The drift handler emails the company contact; UI surfaces a
|
||||
// dashboard tile via /api/extensions/skatteverket/skattekonto/drift.
|
||||
| { type: 'skattekonto.drift_detected'; payload: {
|
||||
drift: number // SKV saldo - GL 1630 sum (signed)
|
||||
saldoSkatteverket: number
|
||||
glSum1630: number
|
||||
fetchedAt: number // ms epoch from the snapshot
|
||||
unbookedCount: number // skattekonto rows without journal_entry_id ≤ fetchedAt
|
||||
userId: string
|
||||
companyId: string
|
||||
} }
|
||||
// Company & account lifecycle
|
||||
| { type: 'company.deleted'; payload: { companyId: string; userId: string; archivedAt: string } }
|
||||
| { type: 'account.deleted'; payload: { userId: string; deletedAt: string } }
|
||||
|
||||
Reference in New Issue
Block a user