namespace DXApp.TemplateKitProject.Services; using DXApp.TemplateKitProject.Models; using System.Xml.Linq; public class ZugferdParserService { // ZUGFeRD v1 Namespace private static readonly XNamespace RsmV1 = "urn:un:unece:uncefact:data:standard:CBFBUY:5"; // ZUGFeRD v2 / Factur-X Namespaces private static readonly XNamespace RsmV2 = "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"; private static readonly XNamespace Ram = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"; private static readonly XNamespace Udt = "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"; // ZUGFeRD v1.0 FeRD (CrossIndustryDocument) private static readonly XNamespace RsmFerd1 = "urn:ferd:CrossIndustryDocument:invoice:1p0"; private static readonly XNamespace RamV12 = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12"; private static readonly XNamespace UdtV15 = "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15"; // NACHHER: public ZugferdInvoice Parse(string xml) { var doc = XDocument.Parse(xml); var root = doc.Root!; if (root.Name.Namespace == RsmV1) return ParseV1(root, xml); if (root.Name.Namespace == RsmFerd1) return ParseV1Ferd(root, xml); return ParseV2(root, xml); } // ── ZUGFeRD v1 (CBFBUY:5) ──────────────────────────────────────────────── private static ZugferdInvoice ParseV1(XElement root, string xml) { // In v1 haben Kind-Elemente KEINEN Namespace-Präfix var ns = XNamespace.None; var rsm = RsmV1; var header = root.Element(rsm + "HeaderExchangedDocument"); var trade = root.Element(rsm + "SpecifiedSupplyChainTradeTransaction"); var agreement = trade?.Element(ns + "ApplicableSupplyChainTradeAgreement"); var settlement = trade?.Element(ns + "ApplicableSupplyChainTradeSettlement"); var seller = agreement?.Element(ns + "SellerTradeParty"); var buyer = agreement?.Element(ns + "BuyerTradeParty"); var summation = settlement?.Element(ns + "SpecifiedTradeSettlementMonetarySummation"); var payment = settlement?.Element(ns + "SpecifiedTradeSettlementPaymentMeans"); // SellerTaxId: das SpecifiedTaxRegistration mit schemeID="VA" nehmen var sellerTaxId = seller? .Elements(ns + "SpecifiedTaxRegistration") .FirstOrDefault(e => (string?)e.Element(ns + "ID")?.Attribute("schemeID") == "VA") ?.Element(ns + "ID")?.Value ?? string.Empty; return new ZugferdInvoice { InvoiceNumber = header?.Element(ns + "ID")?.Value ?? string.Empty, InvoiceDate = ParseDate(header?.Element(ns + "IssueDateTime")?.Value), SellerName = seller?.Element(ns + "Name")?.Value ?? string.Empty, SellerTaxId = sellerTaxId, BuyerName = buyer?.Element(ns + "Name")?.Value ?? string.Empty, CurrencyCode = settlement?.Element(ns + "InvoiceCurrencyCode")?.Value ?? "EUR", TotalAmount = ParseDecimal(summation?.Element(ns + "GrandTotalAmount")?.Value), TaxAmount = ParseDecimal(summation?.Element(ns + "TaxTotalAmount")?.Value), Iban = payment?.Element(ns + "PayeePartyCreditorFinancialAccount") ?.Element(ns + "IBANID")?.Value ?? string.Empty, RawXml = xml, ImportedAt = DateTime.UtcNow }; } // ── ZUGFeRD v2 / Factur-X ──────────────────────────────────────────────── private static ZugferdInvoice ParseV2(XElement root, string xml) { var header = root.Element(RsmV2 + "ExchangedDocument"); var trade = root.Element(RsmV2 + "SupplyChainTradeTransaction"); var agreement = trade?.Element(Ram + "ApplicableHeaderTradeAgreement"); var settlement = trade?.Element(Ram + "ApplicableHeaderTradeSettlement"); return new ZugferdInvoice { InvoiceNumber = header?.Element(Ram + "ID")?.Value ?? string.Empty, InvoiceDate = ParseDate(header?.Element(Ram + "IssueDateTime") ?.Element(Udt + "DateTimeString")?.Value), SellerName = agreement?.Element(Ram + "SellerTradeParty") ?.Element(Ram + "Name")?.Value ?? string.Empty, SellerTaxId = agreement?.Element(Ram + "SellerTradeParty") ?.Element(Ram + "SpecifiedTaxRegistration") ?.Element(Ram + "ID")?.Value ?? string.Empty, BuyerName = agreement?.Element(Ram + "BuyerTradeParty") ?.Element(Ram + "Name")?.Value ?? string.Empty, CurrencyCode = settlement?.Element(Ram + "InvoiceCurrencyCode")?.Value ?? "EUR", TotalAmount = ParseDecimal(settlement ?.Element(Ram + "SpecifiedTradeSettlementHeaderMonetarySummation") ?.Element(Ram + "GrandTotalAmount")?.Value), TaxAmount = ParseDecimal(settlement ?.Element(Ram + "SpecifiedTradeSettlementHeaderMonetarySummation") ?.Element(Ram + "TaxTotalAmount")?.Value), Iban = settlement?.Element(Ram + "SpecifiedTradeSettlementPaymentMeans") ?.Element(Ram + "PayeePartyCreditorFinancialAccount") ?.Element(Ram + "IBANID")?.Value ?? string.Empty, RawXml = xml, ImportedAt = DateTime.UtcNow }; } // ── ZUGFeRD v1.0 FeRD (CrossIndustryDocument:invoice:1p0) ─────────────────── private static ZugferdInvoice ParseV1Ferd(XElement root, string xml) { var rsm = RsmFerd1; var ram = RamV12; var udt = UdtV15; var header = root.Element(rsm + "HeaderExchangedDocument"); var trade = root.Element(rsm + "SpecifiedSupplyChainTradeTransaction"); var agreement = trade?.Element(ram + "ApplicableSupplyChainTradeAgreement"); var settlement = trade?.Element(ram + "ApplicableSupplyChainTradeSettlement"); var seller = agreement?.Element(ram + "SellerTradeParty"); var buyer = agreement?.Element(ram + "BuyerTradeParty"); var summation = settlement?.Element(ram + "SpecifiedTradeSettlementMonetarySummation"); var payment = settlement?.Element(ram + "SpecifiedTradeSettlementPaymentMeans"); var sellerTaxId = seller? .Elements(ram + "SpecifiedTaxRegistration") .FirstOrDefault(e => (string?)e.Element(ram + "ID")?.Attribute("schemeID") == "VA") ?.Element(ram + "ID")?.Value ?? string.Empty; return new ZugferdInvoice { InvoiceNumber = header?.Element(ram + "ID")?.Value ?? string.Empty, InvoiceDate = ParseDate(header?.Element(ram + "IssueDateTime") ?.Element(udt + "DateTimeString")?.Value), SellerName = seller?.Element(ram + "Name")?.Value ?? string.Empty, SellerTaxId = sellerTaxId, BuyerName = buyer?.Element(ram + "Name")?.Value ?? string.Empty, CurrencyCode = settlement?.Element(ram + "InvoiceCurrencyCode")?.Value ?? "EUR", TotalAmount = ParseDecimal(summation?.Element(ram + "GrandTotalAmount")?.Value), TaxAmount = ParseDecimal(summation?.Element(ram + "TaxTotalAmount")?.Value), Iban = payment?.Element(ram + "PayeePartyCreditorFinancialAccount") ?.Element(ram + "IBANID")?.Value ?? string.Empty, RawXml = xml, ImportedAt = DateTime.UtcNow }; } // ── Hilfsmethoden ──────────────────────────────────────────────────────── private static DateTime ParseDate(string? value) { if (string.IsNullOrWhiteSpace(value)) return DateTime.MinValue; return DateTime.TryParseExact(value.Trim(), "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var dt) ? dt : DateTime.MinValue; } private static decimal ParseDecimal(string? value) => decimal.TryParse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var d) ? d : 0m; }