* fix(reports): generate valid two-file NE-bilaga SRU submission (#318, #319) The NE-bilaga "Ladda ner SRU" export produced a file Skatteverket rejects: it was served as UTF-8 text/plain (å/ä/ö mojibake, #319) and was structurally invalid — a single blob with #PRODUKT KONTROLLUPPGIFTER (the KU code), no INFO.SRU/BLANKETTER.SRU split, a #SKAPAT typo, no #FIL_SLUT, and suspect field codes 7310–7350 (#318). Rewrite the generator to mirror the working INK2 generator: a two-file INFO.SRU + BLANKETTER.SRU submission, ISO 8859-1 encoded and zipped, with #PRODUKT SRU, #DATABESKRIVNING_*/#MEDIELEV_*, #BLANKETT NE-<år>P<x>, #IDENTITET <personnummer12> <date> <time>, and #FIL_SLUT. Field codes use the authoritative BAS NE_EJ_K1 coupling table (R1→7400 … R10→7505, R11→7440; period dates 7011/7012). Enskild-firma identity is the owner's 12-digit personnummer (birth-century prefix, not INK2's juridisk-person "16"). - Extract the shared ISO-8859-1 encoder to lib/reports/sru-encoding.ts (was inline in the INK2 route). - Extend the NE engine/types to carry address/postort/email for INFO.SRU. - Frontend: NE SRU download uses the INK2 blob pattern; fix a pre-existing param bug in EfDeclarationSection (fiscal_period_id → period_id, +format=sru). - Add generator tests (structure, BAS field codes, zero-omission, ISO-8859-1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(reports): address review feedback on NE-bilaga SRU generator (#318) - getZipFilename uses the income year (fiscal year END) so the filename matches the blankett type/identity for broken fiscal years. - Refuse to generate a submission when the personnummer is missing/invalid (compute + validate the 12-digit identity once in generateNESRUSubmission and throw) instead of silently emitting a placeholder #IDENTITET that Skatteverket would reject after upload. - validateBlanketterSru now asserts the mandatory räkenskapsår date fields (#UPPGIFT 7011/7012) — their absence is a level-2 rejection. - 10-digit personnummer century is inferred from adult age (≥18, <110) at the income year, fixing the e.g. 1924-born/yy=24 edge that mapped to 2024. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
f8504f3bd0
commit
46039f14f4
@@ -6,6 +6,7 @@ import {
|
||||
} from '@/lib/reports/ink2/sru-generator'
|
||||
import { withRouteContext } from '@/lib/api/with-route-context'
|
||||
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
|
||||
import { encodeISO88591 } from '@/lib/reports/sru-encoding'
|
||||
import JSZip from 'jszip'
|
||||
|
||||
/**
|
||||
@@ -67,13 +68,3 @@ export const GET = withRouteContext(
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/** Encode a string as ISO 8859-1 bytes; characters outside Latin-1 become '?'. */
|
||||
function encodeISO88591(str: string): Uint8Array {
|
||||
const bytes = new Uint8Array(str.length)
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const code = str.charCodeAt(i)
|
||||
bytes[i] = code <= 0xFF ? code : 0x3F
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { generateNEDeclaration } from '@/lib/reports/ne-bilaga/ne-engine'
|
||||
import {
|
||||
generateSRUFile,
|
||||
sruFileToString,
|
||||
getSRUFilename,
|
||||
generateNESRUSubmission,
|
||||
getZipFilename,
|
||||
} from '@/lib/reports/ne-bilaga/sru-generator'
|
||||
import { withRouteContext } from '@/lib/api/with-route-context'
|
||||
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
|
||||
import { encodeISO88591 } from '@/lib/reports/sru-encoding'
|
||||
import JSZip from 'jszip'
|
||||
|
||||
/**
|
||||
* GET /api/reports/ne-bilaga
|
||||
@@ -34,14 +35,23 @@ export const GET = withRouteContext(
|
||||
const declaration = await generateNEDeclaration(supabase, companyId!, periodId)
|
||||
|
||||
if (format === 'sru') {
|
||||
const sruFile = generateSRUFile(declaration)
|
||||
const sruContent = sruFileToString(sruFile)
|
||||
const filename = getSRUFilename(declaration)
|
||||
const submission = generateNESRUSubmission(declaration)
|
||||
|
||||
return new NextResponse(sruContent, {
|
||||
// Skatteverket requires ISO 8859-1 (Latin-1); UTF-8 mojibakes å/ä/ö and is rejected.
|
||||
const infoBytes = encodeISO88591(submission.infoSru)
|
||||
const blanketterBytes = encodeISO88591(submission.blanketterSru)
|
||||
|
||||
const zip = new JSZip()
|
||||
zip.file('INFO.SRU', infoBytes)
|
||||
zip.file('BLANKETTER.SRU', blanketterBytes)
|
||||
|
||||
const zipArrayBuffer = await zip.generateAsync({ type: 'arraybuffer' })
|
||||
const filename = getZipFilename(declaration)
|
||||
|
||||
return new NextResponse(zipArrayBuffer, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Content-Type': 'application/zip',
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
'X-Request-Id': requestId,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user