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:
Mattsson
2026-05-19 16:10:18 +02:00
committed by GitHub
parent e211ab31be
commit 8a6ce7093e
42 changed files with 3420 additions and 206 deletions
+11 -2
View File
@@ -8,6 +8,7 @@ import { Badge } from '@/components/ui/badge'
import { AccountNumber } from '@/components/ui/account-number'
import { AlertCircle, ChevronDown, ChevronRight, Link2, Unlink, Play, Eye } from 'lucide-react'
import { formatCurrency, formatDate } from '@/lib/utils'
import { CashAccountSelector } from '@/components/common/CashAccountSelector'
function formatAmount(amount: number): string {
return amount.toLocaleString('sv-SE', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
@@ -100,6 +101,7 @@ export function BankReconciliationView() {
const [dateFrom, setDateFrom] = useState('')
const [dateTo, setDateTo] = useState('')
const [accountNumber, setAccountNumber] = useState('1930')
const [dryRunResults, setDryRunResults] = useState<DryRunMatch[] | null>(null)
const [runLoading, setRunLoading] = useState(false)
@@ -117,7 +119,8 @@ export function BankReconciliationView() {
const params = new URLSearchParams()
if (dateFrom) params.set('date_from', dateFrom)
if (dateTo) params.set('date_to', dateTo)
const qs = params.toString() ? `?${params}` : ''
params.set('account_number', accountNumber)
const qs = `?${params}`
const [statusRes, glRes, unmatchedRes, matchedRes] = await Promise.all([
fetch(`/api/reconciliation/bank/status${qs}`),
@@ -143,7 +146,7 @@ export function BankReconciliationView() {
} finally {
setLoading(false)
}
}, [dateFrom, dateTo])
}, [dateFrom, dateTo, accountNumber])
useEffect(() => {
fetchAll()
@@ -159,6 +162,7 @@ export function BankReconciliationView() {
body: JSON.stringify({
date_from: dateFrom || undefined,
date_to: dateTo || undefined,
account_number: accountNumber,
dry_run: true,
}),
})
@@ -182,6 +186,7 @@ export function BankReconciliationView() {
body: JSON.stringify({
date_from: dateFrom || undefined,
date_to: dateTo || undefined,
account_number: accountNumber,
dry_run: false,
}),
})
@@ -337,6 +342,10 @@ export function BankReconciliationView() {
<Card>
<CardContent className="pt-6">
<div className="flex flex-wrap items-end gap-4">
<CashAccountSelector
value={accountNumber}
onChange={setAccountNumber}
/>
<div>
<Label>Datum från</Label>
<input