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
+4 -3
View File
@@ -10,6 +10,7 @@ import { ensureArticleNumber } from '@/lib/articles/ensure-article-number'
import { checkRevenueAccount, type RevenueAccountStatus } from '@/lib/articles/validate-revenue-account'
import type { Article } from '@/types'
import type { ArticleImportExecuteResult } from '@/lib/import/articles/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
ensureInitialized()
@@ -140,7 +141,7 @@ export const POST = withRouteContext(
.single()
if (error) {
errors.push({ row_index: row.row_index, name: row.name, reason: error.message })
errors.push({ row_index: row.row_index, name: row.name, reason: getUserErrorMessage(error) })
continue
}
if (data) updated.push(data as Article)
@@ -176,7 +177,7 @@ export const POST = withRouteContext(
skipped++
continue
}
errors.push({ row_index: row.row_index, name: row.name, reason: error.message })
errors.push({ row_index: row.row_index, name: row.name, reason: getUserErrorMessage(error) })
continue
}
@@ -225,7 +226,7 @@ export const POST = withRouteContext(
opLog.error('article import execute failed', err as Error)
return errorResponseFromCode('REG_IMPORT_EXECUTE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+2 -1
View File
@@ -9,6 +9,7 @@ import type {
ArticleImportParseResult,
DetectedArticleColumns,
} from '@/lib/import/articles/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
const ALLOWED_EXTENSIONS = ['.xlsx', '.xls', '.csv', '.ods']
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10 MB
@@ -127,7 +128,7 @@ export const POST = withRouteContext(
opLog.error('article import parse failed', err as Error)
return errorResponseFromCode('REG_IMPORT_PARSE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+3 -2
View File
@@ -9,6 +9,7 @@ import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
import type { ParsedBankTransaction, BankFileFormatId } from '@/lib/import/bank-file/types'
import type { Transaction } from '@/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
ensureInitialized()
@@ -87,7 +88,7 @@ export const POST = withRouteContext(
opLog.error('failed to create bank_file_imports record', importError)
return errorResponseFromCode('BANK_FILE_IMPORT_RECORD_FAILED', opLog, {
requestId,
details: { reason: importError.message },
details: { reason: getUserErrorMessage(importError) },
})
}
@@ -164,7 +165,7 @@ export const POST = withRouteContext(
opLog.error('bank file execute failed', err as Error)
return errorResponseFromCode('BANK_FILE_EXECUTE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+2 -1
View File
@@ -4,6 +4,7 @@ import { decodeFileContent } from '@/lib/import/shared/encoding'
import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
import type { BankFileFormatId } from '@/lib/import/bank-file/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
/**
* POST /api/import/bank-file/parse
@@ -91,7 +92,7 @@ export const POST = withRouteContext(
opLog.error('bank file parse failed', err as Error)
return errorResponseFromCode('BANK_FILE_PARSE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+4 -3
View File
@@ -9,6 +9,7 @@ import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
import type { Customer } from '@/types'
import type { CustomerImportExecuteResult } from '@/lib/import/customers/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
ensureInitialized()
@@ -126,7 +127,7 @@ export const POST = withRouteContext(
.single()
if (error) {
errors.push({ row_index: row.row_index, name: row.name, reason: error.message })
errors.push({ row_index: row.row_index, name: row.name, reason: getUserErrorMessage(error) })
continue
}
if (data) updated.push(data as Customer)
@@ -162,7 +163,7 @@ export const POST = withRouteContext(
skipped++
continue
}
errors.push({ row_index: row.row_index, name: row.name, reason: error.message })
errors.push({ row_index: row.row_index, name: row.name, reason: getUserErrorMessage(error) })
continue
}
if (data) {
@@ -200,7 +201,7 @@ export const POST = withRouteContext(
opLog.error('customer import execute failed', err as Error)
return errorResponseFromCode('REG_IMPORT_EXECUTE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+2 -1
View File
@@ -9,6 +9,7 @@ import type {
CustomerImportParseResult,
DetectedCustomerColumns,
} from '@/lib/import/customers/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
const ALLOWED_EXTENSIONS = ['.xlsx', '.xls', '.csv', '.ods']
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10 MB
@@ -113,7 +114,7 @@ export const POST = withRouteContext(
opLog.error('customer import parse failed', err as Error)
return errorResponseFromCode('REG_IMPORT_PARSE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
@@ -11,6 +11,7 @@ import {
} from '@/lib/import/opening-balance/execute-helpers'
import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponse, errorResponseFromCode } from '@/lib/errors/get-structured-error'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
ensureInitialized()
@@ -244,7 +245,11 @@ export const POST = withRouteContext(
return errorResponseFromCode('OB_CORRECT_FAILED', opLog, {
requestId,
details: { reason, newEntryId: newEntry.id, oldEntryId },
details: {
reason: getUserErrorMessage(seqErr),
newEntryId: newEntry.id,
oldEntryId,
},
})
}
@@ -267,7 +272,7 @@ export const POST = withRouteContext(
if (/Bokföringen är låst/i.test(message)) {
return errorResponseFromCode('OB_COMPANY_LOCK_DATE', opLog, {
requestId,
details: { reason: message },
details: { reason: getUserErrorMessage(err) },
})
}
if (isBookkeepingError(err)) {
@@ -276,7 +281,7 @@ export const POST = withRouteContext(
opLog.error('opening balance correct failed', err as Error)
return errorResponseFromCode('OB_CORRECT_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
@@ -11,6 +11,7 @@ import {
} from '@/lib/import/opening-balance/execute-helpers'
import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponse, errorResponseFromCode } from '@/lib/errors/get-structured-error'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
ensureInitialized()
@@ -129,7 +130,7 @@ export const POST = withRouteContext(
opLog.error('opening balance execute failed', err as Error)
return errorResponseFromCode('OB_EXECUTE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
@@ -3,6 +3,7 @@ import { parseOpeningBalanceFile } from '@/lib/import/opening-balance/parser'
import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
import type { DetectedColumns } from '@/lib/import/opening-balance/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
const ALLOWED_EXTENSIONS = ['.xlsx', '.xls', '.csv', '.ods']
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10 MB
@@ -60,7 +61,7 @@ export const POST = withRouteContext(
opLog.error('opening balance parse failed', err as Error)
return errorResponseFromCode('OB_PARSE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+3 -2
View File
@@ -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'
/**
* GET /api/import/sie/[id]
@@ -18,7 +19,7 @@ export const GET = withRouteContext<{ params: Promise<{ id: string }> }>(
.single()
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
if (!data) {
@@ -68,7 +69,7 @@ export const DELETE = withRouteContext<{ params: Promise<{ id: string }> }>(
.eq('company_id', companyId)
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
return NextResponse.json({ success: true })
+3 -2
View File
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server'
import { withRouteContext } from '@/lib/api/with-route-context'
import type { SIEAccount } from '@/lib/import/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
/**
* Determine account type based on account class (first digit)
@@ -106,7 +107,7 @@ export const POST = withRouteContext(
if (error) {
console.error('Error upserting accounts batch:', error)
return NextResponse.json({
error: `Kunde inte skapa konton (batch ${Math.floor(i / batchSize) + 1}): ${error.message}. ${totalCreated} konton skapades innan felet.`,
error: `Kunde inte skapa konton (batch ${Math.floor(i / batchSize) + 1}): ${getUserErrorMessage(error)}. ${totalCreated} konton skapades innan felet.`,
created: totalCreated,
}, { status: 500 })
}
@@ -123,7 +124,7 @@ export const POST = withRouteContext(
} catch (error) {
console.error('Create accounts error:', error)
return NextResponse.json(
{ error: `Kunde inte skapa konton: ${error instanceof Error ? error.message : 'Okänt fel'}. Försök igen.` },
{ error: `Kunde inte skapa konton: ${error instanceof Error ? getUserErrorMessage(error) : 'Okänt fel'}. Försök igen.` },
{ status: 500 }
)
}
+2 -1
View File
@@ -6,6 +6,7 @@ import { BAS_REFERENCE } from '@/lib/bookkeeping/bas-data'
import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
import type { AccountMapping, SIEAccountMappingRecord } from '@/lib/import/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
// SIE imports with many vouchers need extended execution time
export const maxDuration = 300
@@ -125,7 +126,7 @@ export const POST = withRouteContext(
opLog.error('sie execute unexpected error', err as Error)
return errorResponseFromCode('SIE_IMPORT_UNEXPECTED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+6 -5
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
import { withRouteContext } from '@/lib/api/with-route-context'
import { saveMappings } from '@/lib/import/sie-import'
import type { AccountMapping } from '@/lib/import/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
/**
* GET /api/import/sie/mappings
@@ -17,7 +18,7 @@ export const GET = withRouteContext(
.order('source_account')
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
return NextResponse.json({ data })
@@ -43,7 +44,7 @@ export const POST = withRouteContext(
return NextResponse.json({ success: true })
} catch (error) {
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to save mappings' },
{ error: error instanceof Error ? getUserErrorMessage(error) : 'Failed to save mappings' },
{ status: 500 }
)
}
@@ -84,7 +85,7 @@ export const PUT = withRouteContext(
.single()
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
return NextResponse.json({ data })
@@ -111,7 +112,7 @@ export const DELETE = withRouteContext(
.eq('source_account', sourceAccount)
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
} else {
// Delete all mappings
@@ -121,7 +122,7 @@ export const DELETE = withRouteContext(
.eq('company_id', companyId)
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
}
+2 -1
View File
@@ -12,6 +12,7 @@ import { BAS_REFERENCE } from '@/lib/bookkeeping/bas-data'
import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
import type { SIEAccountMappingRecord } from '@/lib/import/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
/**
* POST /api/import/sie/parse
@@ -143,7 +144,7 @@ export const POST = withRouteContext(
opLog.error('sie parse failed', err as Error)
return errorResponseFromCode('SIE_PARSE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+2 -1
View File
@@ -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'
/**
* GET /api/import/sie
@@ -28,7 +29,7 @@ export const GET = withRouteContext(
const { data, error, count } = await query
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json({ error: getUserErrorMessage(error) }, { status: 500 })
}
return NextResponse.json({
+4 -3
View File
@@ -9,6 +9,7 @@ import { withRouteContext } from '@/lib/api/with-route-context'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
import type { Supplier } from '@/types'
import type { SupplierImportExecuteResult } from '@/lib/import/suppliers/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
ensureInitialized()
@@ -110,7 +111,7 @@ export const POST = withRouteContext(
.single()
if (error) {
errors.push({ row_index: row.row_index, name: row.name, reason: error.message })
errors.push({ row_index: row.row_index, name: row.name, reason: getUserErrorMessage(error) })
continue
}
if (data) updated.push(data as Supplier)
@@ -150,7 +151,7 @@ export const POST = withRouteContext(
skipped++
continue
}
errors.push({ row_index: row.row_index, name: row.name, reason: error.message })
errors.push({ row_index: row.row_index, name: row.name, reason: getUserErrorMessage(error) })
continue
}
if (data) {
@@ -185,7 +186,7 @@ export const POST = withRouteContext(
opLog.error('supplier import execute failed', err as Error)
return errorResponseFromCode('REG_IMPORT_EXECUTE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},
+2 -1
View File
@@ -9,6 +9,7 @@ import type {
SupplierImportParseResult,
DetectedSupplierColumns,
} from '@/lib/import/suppliers/types'
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
const ALLOWED_EXTENSIONS = ['.xlsx', '.xls', '.csv', '.ods']
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10 MB
@@ -106,7 +107,7 @@ export const POST = withRouteContext(
opLog.error('supplier import parse failed', err as Error)
return errorResponseFromCode('REG_IMPORT_PARSE_FAILED', opLog, {
requestId,
details: { reason: err instanceof Error ? err.message : 'unknown' },
details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' },
})
}
},