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:
@@ -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) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user