fix(import): SIE bulk-delete on service client + provider/reporting/b… (#724)

* fix(import): SIE bulk-delete on service client + provider/reporting/banking fixes

Rebuilt branch onto main as a single commit.

- import: run SIE bulk-delete RPCs on the service client to escape the 8s
  statement_timeout; undo_sie_import now takes an explicit actor (p_user_id)
  so its owner/admin gate works when auth.uid() is NULL on the service
  client (migration 20260624120000) + pg-real regression test
- providers: distinguish missing Fortnox license from expired connection;
  provider_consent_tokens PK regression test
- reports: include unmapped BAS expense groups in the income statement
- enable-banking: reconnect closed/expired bank sessions in place
- bookkeeping: surface linked invoices as underlag on the verifikat view
- scripts: track BL cleanup/diagnostic tooling; data files (*.csv) are
  git-ignored and consentId is now a required arg with no silent default

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(import): add Cache-Control header to journal entry references response

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-06-14 23:40:26 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent db8983ba9e
commit 43925bc2d3
37 changed files with 2456 additions and 1152 deletions
@@ -23,7 +23,7 @@ interface BankConnectionStatusProps {
connection: BankConnection
onSync: (connectionId: string) => void
onDisconnect: (connectionId: string) => void
onReconnect?: (bank: { name: string; country: string }) => void
onReconnect?: (connection: BankConnection) => void
onManageAccounts?: (connectionId: string) => void
isSyncing?: boolean
}
@@ -132,21 +132,18 @@ export function BankConnectionStatus({
</div>
</div>
<div className="flex items-center gap-2">
{isConnectionExpired && onReconnect && (
{(isConnectionExpired || isConnectionError) && onReconnect && (
<Button
variant="outline"
size="sm"
onClick={() => onReconnect({
name: connection.bank_name,
country: (connection.provider as string)?.split('-').pop()?.toUpperCase() || 'SE',
})}
onClick={() => onReconnect(connection)}
>
Förnya anslutning
</Button>
)}
{isConnectionError && (
<Button
variant="outline"
variant="ghost"
size="sm"
onClick={() => onSync(connection.id)}
disabled={isSyncing}
@@ -165,6 +165,52 @@ export default function BankingSettingsPanel() {
}
}
// Re-authorize an existing connection in place — no disconnect required.
// Posts to /connect with the existing connection_id so the server reuses the
// same row (revoking the dead session, issuing fresh authorization), then
// hands off to the bank's consent screen. The OAuth callback drives the row
// back through account selection to active.
async function handleReconnect(connection: BankConnection) {
if (connectingRef.current) return
connectingRef.current = true
setIsConnecting(true)
setConnectingBankName(connection.bank_name)
try {
const country = (connection.provider as string)?.split('-').pop()?.toUpperCase() || 'SE'
const response = await fetch('/api/extensions/ext/enable-banking/connect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
connection_id: connection.id,
aspsp_name: connection.bank_name,
aspsp_country: country,
}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error)
}
window.location.href = data.authorization_url
} catch (error) {
console.error('[enable-banking] Reconnect flow failed', {
message: error instanceof Error ? error.message : String(error),
connectionId: connection.id,
})
toast({
title: 'Fel',
description: error instanceof Error ? error.message : 'Kunde inte förnya anslutningen',
variant: 'destructive',
})
connectingRef.current = false
setIsConnecting(false)
setConnectingBankName(null)
}
}
async function handleSyncTransactions(connectionId: string) {
setSyncingConnectionId(connectionId)
@@ -214,6 +260,9 @@ export default function BankingSettingsPanel() {
variant: 'destructive',
})
setShowCsvFallback(true)
// Refresh so a now-expired connection (e.g. closed PSD2 session) moves
// into "Åtgärd krävs" and surfaces the "Förnya anslutning" button.
fetchConnections()
}
setSyncingConnectionId(null)
@@ -382,7 +431,7 @@ export default function BankingSettingsPanel() {
connection={connection}
onSync={handleSyncTransactions}
onDisconnect={handleDisconnectBank}
onReconnect={handleConnectBank}
onReconnect={handleReconnect}
onManageAccounts={() => setPickerConnectionId(connection.id)}
isSyncing={syncingConnectionId === connection.id}
/>