Bug/gh issues fiz (#1103)

* refactor: optimize page loading and data fetching

* fix: resolve recurring production runtime errors

* feat: add MCP company and customer updates

* fix: handle year-end tax adjustments

* feat: harden annual report compliance

* fix: expand invoice logo and font support

* fix: sanitize API route error responses

* fix: sanitize user-facing error messages

* feat: persist onboarding and tax assessment notices

* fix: reduce cloud backup audit churn

* feat: refine invoice editor layout

* fix: show saved tax adjustments in INK2

* fix: complete annual report API mappings

* docs: record operational safeguards and decisions

* fix: harden annual report review findings

* fix: adjust column span for description based on VAT registration

* New css class name
This commit is contained in:
Mattsson
2026-07-21 23:00:15 +02:00
committed by GitHub
parent 702512437a
commit e11f70b347
467 changed files with 17569 additions and 2221 deletions
@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server'
import { withRouteContext } from '@/lib/api/with-route-context'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
export const GET = withRouteContext<{ params: Promise<{ sector: string; slug: string }> }>(
'extension.data.get',
@@ -28,7 +29,7 @@ export const GET = withRouteContext<{ params: Promise<{ sector: string; slug: st
const { data, error } = await query
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
return NextResponse.json({ data })
@@ -65,7 +66,7 @@ export const POST = withRouteContext<{ params: Promise<{ sector: string; slug: s
.single()
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
return NextResponse.json({ data })
@@ -95,7 +96,7 @@ export const DELETE = withRouteContext<{ params: Promise<{ sector: string; slug:
.eq('key', key)
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
return NextResponse.json({ success: true })
@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server'
import { withRouteContext } from '@/lib/api/with-route-context'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
export const GET = withRouteContext<{ params: Promise<{ sector: string; slug: string }> }>(
'extension.settings.get',
@@ -55,7 +56,7 @@ export const PATCH = withRouteContext<{ params: Promise<{ sector: string; slug:
.single()
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
return NextResponse.json({ data: data.value })
@@ -270,7 +270,7 @@ describe('cloud-backup auto-sync cron', () => {
companyId: 'c-1',
kind: 'repeated_failures',
consecutiveFailures: 3,
errorMessage: 'Drive upload failed: 500',
errorMessage: 'Något gick fel. Försök igen.',
})
)
const [, , , , value] = mockSaveExtensionData.mock.calls[0]
@@ -330,7 +330,7 @@ describe('cloud-backup auto-sync cron', () => {
)
const [, , , , value] = mockSaveExtensionData.mock.calls[0]
expect((value as any).last_auto_sync_status).toBe('error')
expect((value as any).last_auto_sync_error).toContain('Drive quota exceeded')
expect((value as any).last_auto_sync_error).toBe('Något gick fel. Försök igen.')
expect((value as any).consecutive_failures).toBe(3)
})
@@ -2,6 +2,7 @@ import { createClient } from '@supabase/supabase-js'
import { NextResponse } from 'next/server'
import { withCronContext } from '@/lib/api/with-cron-context'
import { errorResponse, errorResponseFromCode } from '@/lib/errors/get-structured-error'
import { getErrorMessage } from '@/lib/errors/get-error-message'
import {
performSync,
CONNECTION_KEY,
@@ -210,6 +211,7 @@ export const GET = withCronContext('cron.cloud_backup_auto_sync', async (_reques
const consecutiveFailures = syncResult.ok
? 0
: (schedule.consecutive_failures ?? 0) + 1
const safeSyncError = syncResult.ok ? null : getErrorMessage(syncResult.message)
let lastAlertAt = schedule.last_alert_at ?? null
if (!syncResult.ok) {
lastAlertAt = await maybeAlert({
@@ -217,7 +219,7 @@ export const GET = withCronContext('cron.cloud_backup_auto_sync', async (_reques
userId,
kind: syncResult.reason === 'needs_reauth' ? 'needs_reauth' : 'repeated_failures',
consecutiveFailures,
errorMessage: syncResult.message,
errorMessage: safeSyncError,
lastAlertAt,
})
}
@@ -226,7 +228,7 @@ export const GET = withCronContext('cron.cloud_backup_auto_sync', async (_reques
...schedule,
last_auto_sync_at: new Date().toISOString(),
last_auto_sync_status: syncResult.ok ? 'success' : 'error',
last_auto_sync_error: syncResult.ok ? null : syncResult.message,
last_auto_sync_error: safeSyncError,
consecutive_failures: consecutiveFailures,
last_alert_at: lastAlertAt,
}
@@ -235,10 +237,10 @@ export const GET = withCronContext('cron.cloud_backup_auto_sync', async (_reques
results.push({
companyId,
status: syncResult.ok ? 'success' : 'error',
error: syncResult.ok ? undefined : syncResult.message,
error: safeSyncError ?? undefined,
})
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error'
const safeMessage = getErrorMessage(err)
ctx.log.error('cloud backup sync failed for company', err as Error, {
companyId,
})
@@ -249,7 +251,7 @@ export const GET = withCronContext('cron.cloud_backup_auto_sync', async (_reques
userId,
kind: 'repeated_failures',
consecutiveFailures,
errorMessage: message.slice(0, 200),
errorMessage: safeMessage.slice(0, 200),
lastAlertAt: schedule.last_alert_at,
})
@@ -257,7 +259,7 @@ export const GET = withCronContext('cron.cloud_backup_auto_sync', async (_reques
...schedule,
last_auto_sync_at: new Date().toISOString(),
last_auto_sync_status: 'error',
last_auto_sync_error: message.slice(0, 200),
last_auto_sync_error: safeMessage.slice(0, 200),
consecutive_failures: consecutiveFailures,
last_alert_at: lastAlertAt,
}
@@ -267,7 +269,7 @@ export const GET = withCronContext('cron.cloud_backup_auto_sync', async (_reques
},
)
results.push({ companyId, status: 'error', error: message })
results.push({ companyId, status: 'error', error: safeMessage })
}
}
@@ -459,7 +459,10 @@ describe('AGI kvittenser cron', () => {
const body = await res.json()
expect(body.errors).toBe(1)
expect(body.results[0]).toMatchObject({ status: 'error', error: 'fetch failed' })
expect(body.results[0]).toMatchObject({
status: 'error',
error: 'Något gick fel. Försök igen.',
})
expect(errorRecorder).toHaveBeenCalledTimes(1)
expect(String(errorRecorder.mock.calls[0][0])).toContain('Reconciliation failed')
})
@@ -11,6 +11,7 @@ import { reconcileAgiDeclaration } from '@/extensions/general/skatteverket/lib/a
import { formatRedovisningsperiod } from '@/lib/skatteverket/format'
import { hasCapability } from '@/lib/entitlements/has-capability'
import { CAPABILITY } from '@/lib/entitlements/keys'
import { getErrorMessage } from '@/lib/errors/get-error-message'
ensureInitialized()
@@ -204,7 +205,7 @@ export async function GET(request: Request) {
}
log.error('Reconciliation failed', { declarationId, companyId, period, message })
results.push({ declarationId, period, status: 'error', error: message })
results.push({ declarationId, period, status: 'error', error: getErrorMessage(err) })
}
}
@@ -12,6 +12,7 @@ import { SkatteverketSkattekontoError } from '@/extensions/general/skatteverket/
import { markNeedsReconsent, RECONSENT_ERROR_CODES } from '@/extensions/general/skatteverket/lib/token-store'
import { getSystemAuthMode, isSystemAuthConfigured } from '@/extensions/general/skatteverket/lib/system-auth/config'
import { listVerifiedCompanies, markGrantRevoked } from '@/extensions/general/skatteverket/lib/connection-store'
import { getErrorMessage } from '@/lib/errors/get-error-message'
import { currentSkvEnvironment, hasVerifiedGrant } from '@/extensions/general/skatteverket/lib/resolve-auth'
ensureInitialized()
@@ -261,7 +262,7 @@ export async function GET(request: Request) {
message,
felkod,
})
results.push({ userId, companyId, source, status: 'error', error: message })
results.push({ userId, companyId, source, status: 'error', error: getErrorMessage(err) })
}
}
@@ -298,7 +298,10 @@ describe('VAT kvittenser cron', () => {
const body = await res.json()
expect(body.errors).toBe(1)
expect(body.results[0]).toMatchObject({ status: 'error', error: 'fetch failed' })
expect(body.results[0]).toMatchObject({
status: 'error',
error: 'Något gick fel. Försök igen.',
})
expect(errorSpy).toHaveBeenCalledTimes(1)
})
@@ -12,6 +12,7 @@ import { hasCapability } from '@/lib/entitlements/has-capability'
import { CAPABILITY } from '@/lib/entitlements/keys'
import type { SkatteverketInlamnatResponse } from '@/extensions/general/skatteverket/types'
import type { VatPeriodType } from '@/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
ensureInitialized()
@@ -183,7 +184,7 @@ export async function GET(request: Request) {
companyId,
period,
status: 'error',
error: `Failed to persist signed state: ${updateError.message}`,
error: `Failed to persist signed state: ${getUserErrorMessage(updateError)}`,
})
continue
}
@@ -283,7 +284,7 @@ export async function GET(request: Request) {
}
console.error('[vat-kvittenser-cron] Reconciliation failed', { companyId, period, message })
results.push({ companyId, period, status: 'error', error: message })
results.push({ companyId, period, status: 'error', error: getUserErrorMessage(err) })
}
}