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