feat: INK2 declaration improvements, invoice delivery date & Swedish compliance skills (#204)
* feat: add INK2 declaration improvements, invoice delivery date, and Swedish compliance skills Expand INK2 engine with full INK2S/INK2R support and improved SRU generation. Add delivery_date field to invoices and corresponding PDF/migration support. Add Claude skills for Swedish asset accounting, invoice compliance, SIE import/export, SRU filing, and tax planning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — map BAS 4500–4899, strip CRLF in SRU, document P3 - Map BAS accounts 4500–4599 (legoarbeten), 4700–4899 (diverse varuinköpskostnader) to SRU 7512 so they are not silently dropped from INK2R declarations - Strip \r\n in sanitizeString to prevent CRLF injection in SRU fields - Document P3 period suffix limitation for brutet räkenskapsår Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: correct BAS 4500-4599, 4700-4899 mapping from 7512 to 7511 Per the official BAS-to-SRU mapping, these account ranges are cost of goods (legoarbeten, inkurans, svinn) and belong under 7511 (Råvaror och förnödenheter), not 7512 (Handelsvaror). 7512 remains 4600-4699. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
bf5a8d9195
commit
7a18d89c70
@@ -24,7 +24,7 @@ export async function POST(request: Request) {
|
||||
const companyId = await requireCompanyId(supabase, user.id)
|
||||
|
||||
const body = await request.json()
|
||||
const { customer_id, invoice_date, due_date, currency, items, your_reference, our_reference, notes, document_type } = body
|
||||
const { customer_id, invoice_date, due_date, delivery_date, currency, items, your_reference, our_reference, notes, document_type } = body
|
||||
|
||||
if (!customer_id || !items || items.length === 0) {
|
||||
return NextResponse.json({ error: 'Kunduppgifter och rader krävs' }, { status: 400 })
|
||||
@@ -93,6 +93,7 @@ export async function POST(request: Request) {
|
||||
invoice_number: 'FÖRHANDSGRANSKNING',
|
||||
invoice_date: invoice_date || new Date().toISOString().split('T')[0],
|
||||
due_date: due_date || new Date().toISOString().split('T')[0],
|
||||
delivery_date: delivery_date || null,
|
||||
status: 'draft',
|
||||
currency: currency || 'SEK',
|
||||
exchange_rate: null,
|
||||
|
||||
@@ -188,6 +188,7 @@ export async function POST(request: Request) {
|
||||
invoice_number: invoiceNumber,
|
||||
invoice_date: invoiceInput.invoice_date,
|
||||
due_date: invoiceInput.due_date,
|
||||
delivery_date: invoiceInput.delivery_date ?? null,
|
||||
currency: invoiceInput.currency,
|
||||
exchange_rate: exchangeRate,
|
||||
exchange_rate_date: exchangeRateDate,
|
||||
@@ -312,6 +313,7 @@ async function createCreditNote(
|
||||
invoice_number: creditNoteNumber,
|
||||
invoice_date: new Date().toISOString().split('T')[0],
|
||||
due_date: new Date().toISOString().split('T')[0],
|
||||
delivery_date: originalInvoice.delivery_date ?? null,
|
||||
currency: originalInvoice.currency,
|
||||
exchange_rate: originalInvoice.exchange_rate,
|
||||
exchange_rate_date: originalInvoice.exchange_rate_date,
|
||||
|
||||
@@ -2,11 +2,11 @@ import { createClient } from '@/lib/supabase/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { generateINK2Declaration } from '@/lib/reports/ink2/ink2-engine'
|
||||
import {
|
||||
generateSRUFile,
|
||||
sruFileToString,
|
||||
getSRUFilename,
|
||||
generateSRUSubmission,
|
||||
getZipFilename,
|
||||
} from '@/lib/reports/ink2/sru-generator'
|
||||
import { requireCompanyId } from '@/lib/company/context'
|
||||
import JSZip from 'jszip'
|
||||
|
||||
/**
|
||||
* GET /api/reports/ink2
|
||||
@@ -15,7 +15,7 @@ import { requireCompanyId } from '@/lib/company/context'
|
||||
*
|
||||
* Query parameters:
|
||||
* - period_id: Fiscal period ID (required)
|
||||
* - format: 'json' (default) or 'sru' for SRU file download
|
||||
* - format: 'json' (default) or 'sru' for SRU file download (ZIP with INFO.SRU + BLANKETTER.SRU)
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
const supabase = await createClient()
|
||||
@@ -42,14 +42,24 @@ export async function GET(request: Request) {
|
||||
const declaration = await generateINK2Declaration(supabase, companyId, periodId)
|
||||
|
||||
if (format === 'sru') {
|
||||
const sruFile = generateSRUFile(declaration)
|
||||
const sruContent = sruFileToString(sruFile)
|
||||
const filename = getSRUFilename(declaration)
|
||||
const submission = generateSRUSubmission(declaration)
|
||||
|
||||
return new NextResponse(sruContent, {
|
||||
// Encode both files as ISO 8859-1 (Latin-1) — required by Skatteverket
|
||||
const infoBytes = encodeISO88591(submission.infoSru)
|
||||
const blanketterBytes = encodeISO88591(submission.blanketterSru)
|
||||
|
||||
// Create ZIP with both files
|
||||
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}"`,
|
||||
},
|
||||
})
|
||||
@@ -64,3 +74,16 @@ export async function GET(request: Request) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a string as ISO 8859-1 (Latin-1) bytes.
|
||||
* Characters outside the Latin-1 range are replaced with '?'.
|
||||
*/
|
||||
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 // '?' for unmappable chars
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user