* 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
@@ -361,11 +361,11 @@ export function EfDeclarationSection({
|
||||
<CardContent>
|
||||
<Button asChild variant="outline">
|
||||
<Link
|
||||
href={`/api/reports/ne-bilaga?fiscal_period_id=${fiscalPeriodId}`}
|
||||
href={`/api/reports/ne-bilaga?period_id=${fiscalPeriodId}&format=sru`}
|
||||
prefetch={false}
|
||||
>
|
||||
<FileDown className="mr-2 h-4 w-4" />
|
||||
Förhandsgranska NE-bilaga
|
||||
Ladda ner NE-bilaga (SRU)
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { formatCurrency } from '@/lib/utils'
|
||||
export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
const [data, setData] = useState<NEDeclaration | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [downloading, setDownloading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetchDeclaration = async () => {
|
||||
@@ -32,8 +33,24 @@ export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
const downloadSRU = () => {
|
||||
window.open(`/api/reports/ne-bilaga?period_id=${periodId}&format=sru`, '_blank')
|
||||
const downloadSRU = async () => {
|
||||
setDownloading(true)
|
||||
try {
|
||||
const res = await fetch(`/api/reports/ne-bilaga?period_id=${periodId}&format=sru`)
|
||||
if (!res.ok) throw new Error('Download failed')
|
||||
const blob = await res.blob()
|
||||
const filename = res.headers.get('Content-Disposition')?.match(/filename="(.+)"/)?.[1] || 'NE_SRU.zip'
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filename
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch {
|
||||
setError('Kunde inte ladda ner SRU-filer')
|
||||
} finally {
|
||||
setDownloading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// NE ruta labels
|
||||
@@ -72,9 +89,9 @@ export function NEDeclarationView({ periodId }: { periodId: string }) {
|
||||
{loading ? 'Laddar...' : 'Hämta NE-bilaga'}
|
||||
</Button>
|
||||
{data && (
|
||||
<Button variant="outline" onClick={downloadSRU}>
|
||||
<Button variant="outline" onClick={downloadSRU} disabled={downloading}>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Ladda ner SRU-fil
|
||||
{downloading ? 'Laddar ner...' : 'Ladda ner SRU-fil'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user