/**
* EU Sales List XML Generator (SKV 5740 format)
*
* Generates XML compatible with Skatteverket's e-filing system for
* periodisk sammanställning (EC Sales List / recapitulative statement).
*
* Reference: Skatteverket SKV 5740, KVPS XML schema
* Filing: Monthly for goods, quarterly for services
*
* The XML structure follows Skatteverket's KVPS (Kvartalsvis Periodisk
* Sammanställning) format with elements for reporter info, period, and
* per-customer goods/services/triangulation amounts in whole SEK.
*/
import type { ECSalesListReport } from './eu-sales-list-engine'
/**
* Generate SKV-compatible XML for the EC Sales List report.
*
* Structure:
*
* — reporter/sender information
* — reporting period
* — one per customer VAT number
* — buyer VAT number
* — buyer country code
* — goods amount (box 35)
* — services amount (box 39)
* — triangulation (box 38)
*
*
*
* All amounts are rounded to whole SEK (no decimals).
*/
export function generateSKVXml(report: ECSalesListReport): string {
const lines: string[] = []
lines.push('')
lines.push('')
// Reporter/sender info
lines.push(' ')
lines.push(` ${escapeXml(report.reporterVatNumber)}`)
lines.push(` ${escapeXml(report.reporterName)}`)
lines.push(' ')
// Period info
lines.push(' ')
lines.push(` ${report.period.year}`)
if (report.period.month !== undefined) {
lines.push(` ${String(report.period.month).padStart(2, '0')}`)
}
if (report.period.quarter !== undefined) {
lines.push(` ${report.period.quarter}`)
}
lines.push(` ${report.filingType === 'monthly' ? 'Manad' : 'Kvartal'}`)
lines.push(' ')
// Customer lines
for (const line of report.lines) {
const goods = Math.round(line.goodsAmount)
const services = Math.round(line.servicesAmount)
const triangulation = Math.round(line.triangulationAmount)
// Skip lines with all zero amounts
if (goods === 0 && services === 0 && triangulation === 0) continue
lines.push(' ')
lines.push(` ${escapeXml(line.customerVatNumber)}`)
lines.push(` ${escapeXml(line.customerCountry)}`)
if (goods !== 0) {
lines.push(` ${goods}`)
}
if (services !== 0) {
lines.push(` ${services}`)
}
if (triangulation !== 0) {
lines.push(` ${triangulation}`)
}
lines.push(' ')
}
// Totals
lines.push(' ')
lines.push(` ${Math.round(report.totals.goods)}`)
lines.push(` ${Math.round(report.totals.services)}`)
lines.push(` ${Math.round(report.totals.triangulation)}`)
lines.push(` ${Math.round(report.totals.total)}`)
lines.push(' ')
lines.push('')
return lines.join('\n') + '\n'
}
/**
* Generate a filename for the XML download.
*
* Format: KVPS__.xml
* Example: KVPS_SE556677889901_2026-Q1.xml
*/
export function generateXMLFilename(report: ECSalesListReport): string {
const vat = report.reporterVatNumber.replace(/\s/g, '')
const period = formatPeriod(report)
return `KVPS_${vat}_${period}.xml`
}
function formatPeriod(report: ECSalesListReport): string {
const { year, month, quarter } = report.period
if (month !== undefined) {
return `${year}-${String(month).padStart(2, '0')}`
}
if (quarter !== undefined) {
return `${year}-Q${quarter}`
}
return `${year}`
}
/** Escape special XML characters to prevent injection */
function escapeXml(str: string): string {
return str
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}