Files
FEDEO/backend/tests/einvoice.test.ts

69 lines
6.3 KiB
TypeScript

import assert from "node:assert/strict"
import test from "node:test"
import { PDFDocument } from "pdf-lib"
import { detectElectronicInvoice } from "../src/modules/einvoice/detectElectronicInvoice"
import { parseElectronicInvoice } from "../src/modules/einvoice/parseElectronicInvoice"
import { mapElectronicInvoiceToIncomingInvoice } from "../src/modules/einvoice/mapElectronicInvoice"
const ciiInvoice = Buffer.from(`<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext><ram:GuidelineSpecifiedDocumentContextParameter><ram:ID>urn:factur-x.eu:1p0:en16931</ram:ID></ram:GuidelineSpecifiedDocumentContextParameter></rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument><ram:ID>RE-2026-100</ram:ID><ram:TypeCode>380</ram:TypeCode><ram:IssueDateTime><udt:DateTimeString format="102">20260721</udt:DateTimeString></ram:IssueDateTime></rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:IncludedSupplyChainTradeLineItem>
<ram:SpecifiedTradeProduct><ram:Name>Wartung</ram:Name></ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeDelivery><ram:BilledQuantity unitCode="HUR">2</ram:BilledQuantity></ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement><ram:ApplicableTradeTax><ram:CategoryCode>S</ram:CategoryCode><ram:RateApplicablePercent>19</ram:RateApplicablePercent></ram:ApplicableTradeTax><ram:SpecifiedTradeSettlementLineMonetarySummation><ram:LineTotalAmount>100.00</ram:LineTotalAmount></ram:SpecifiedTradeSettlementLineMonetarySummation></ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>
<ram:ApplicableHeaderTradeAgreement><ram:BuyerReference>Leitweg-1</ram:BuyerReference><ram:SellerTradeParty><ram:Name>Beispiel GmbH</ram:Name><ram:SpecifiedTaxRegistration><ram:ID schemeID="VA">DE123456789</ram:ID></ram:SpecifiedTaxRegistration></ram:SellerTradeParty></ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeSettlement><ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode><ram:SpecifiedTradeSettlementPaymentMeans><ram:TypeCode>58</ram:TypeCode></ram:SpecifiedTradeSettlementPaymentMeans><ram:SpecifiedTradePaymentTerms><ram:DueDateDateTime><udt:DateTimeString format="102">20260820</udt:DateTimeString></ram:DueDateDateTime></ram:SpecifiedTradePaymentTerms><ram:SpecifiedTradeSettlementHeaderMonetarySummation><ram:LineTotalAmount>100.00</ram:LineTotalAmount><ram:TaxTotalAmount>19.00</ram:TaxTotalAmount><ram:GrandTotalAmount>119.00</ram:GrandTotalAmount><ram:DuePayableAmount>119.00</ram:DuePayableAmount></ram:SpecifiedTradeSettlementHeaderMonetarySummation></ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>`)
const ublInvoice = Buffer.from(`<?xml version="1.0" encoding="UTF-8"?>
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0</cbc:CustomizationID><cbc:ID>XR-42</cbc:ID><cbc:IssueDate>2026-07-21</cbc:IssueDate><cbc:DueDate>2026-08-20</cbc:DueDate><cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty><cac:Party><cac:PartyName><cbc:Name>UBL Lieferant</cbc:Name></cac:PartyName><cac:PartyTaxScheme><cbc:CompanyID>DE987654321</cbc:CompanyID><cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:PartyTaxScheme></cac:Party></cac:AccountingSupplierParty>
<cac:PaymentMeans><cbc:PaymentMeansCode>30</cbc:PaymentMeansCode></cac:PaymentMeans>
<cac:TaxTotal><cbc:TaxAmount currencyID="EUR">7.00</cbc:TaxAmount></cac:TaxTotal>
<cac:LegalMonetaryTotal><cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount><cbc:TaxInclusiveAmount currencyID="EUR">107.00</cbc:TaxInclusiveAmount><cbc:PayableAmount currencyID="EUR">107.00</cbc:PayableAmount></cac:LegalMonetaryTotal>
<cac:InvoiceLine><cbc:ID>1</cbc:ID><cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity><cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount><cac:Item><cbc:Name>Material</cbc:Name><cac:ClassifiedTaxCategory><cbc:ID>S</cbc:ID><cbc:Percent>7</cbc:Percent></cac:ClassifiedTaxCategory></cac:Item></cac:InvoiceLine>
</ubl:Invoice>`)
test("erkennt und liest eine eingebettete Factur-X-Rechnung", async () => {
const document = await PDFDocument.create()
document.addPage()
await document.attach(ciiInvoice, "factur-x.xml", { mimeType: "application/xml", afRelationship: "Data" })
const detection = await detectElectronicInvoice(Buffer.from(await document.save()), { name: "rechnung.pdf" })
assert.equal(detection?.container, "pdf")
assert.equal(detection?.syntax, "cii")
const parsed = parseElectronicInvoice(detection!.xml, detection!.syntax)
assert.equal(parsed.invoiceNumber, "RE-2026-100")
assert.equal(parsed.seller.vatId, "DE123456789")
assert.equal(parsed.items[0].grossAmount, 119)
assert.deepEqual(parsed.validation.errors, [])
})
test("liest eine eigenständige UBL-XRechnung und mappt sie nach FEDEO", async () => {
const detection = await detectElectronicInvoice(ublInvoice, { name: "xrechnung.xml" })
assert.equal(detection?.syntax, "ubl-invoice")
const parsed = parseElectronicInvoice(detection!.xml, detection!.syntax)
const mapped = mapElectronicInvoiceToIncomingInvoice(parsed, 5, 12, "xml", "xrechnung.xml")
assert.equal(mapped.reference, "XR-42")
assert.equal(mapped.vendor, 12)
assert.equal(mapped.paymentType, "Überweisung")
assert.equal(mapped.state, "Vorbereitet")
assert.equal(mapped.accounts[0].amountTax, 7)
})
test("markiert Summenabweichungen als Prüfungsfehler", () => {
const invalid = Buffer.from(ciiInvoice.toString("utf8").replace("<ram:LineTotalAmount>100.00</ram:LineTotalAmount><ram:TaxTotalAmount>", "<ram:LineTotalAmount>90.00</ram:LineTotalAmount><ram:TaxTotalAmount>"))
const parsed = parseElectronicInvoice(invalid, "cii")
assert.match(parsed.validation.errors.join(" "), /Positionssumme/)
})