UNPKG

@stafyniaksacha/facturx

Version:

Factur-X and Order-X generation library for European e-invoicing standard

220 lines (211 loc) 8.78 kB
import { randomBytes } from 'node:crypto'; import { PDFName, PDFHexString, AFRelationship } from 'pdf-lib'; import { p as pkg } from './facturx.DVqxPZ0W.mjs'; import { D as DOC_TYPE, a as resolveXml, g as getFlavor, b as getLevel, c as check, e as extractBaseInfo, r as resolvePdf, Z as ZUGFERD_FILENAMES, O as ORDERX_FILENAME, F as FACTURX_FILENAME, d as FACTURX_CONFORMANCE_LEVEL } from './facturx.BLNAZCZq.mjs'; import { format } from 'date-fns'; function baseInfo2PdfMetadata(info) { let title = ""; let subject = ""; let doc_x = ""; let author = ""; const doc_type_name = DOC_TYPE[info.docType] || "Invoice"; const date = format(info.date, "yyyyMMdd"); if (info.docType === "231") { title = `${info.seller}: Order Response on Order ${info.number} from ${info.buyer}`; subject = `Response of ${info.seller} on ${date} to order ${info.number} from ${info.buyer}`; doc_x = `Order-X`; author = info.seller; } else if (["220", "230"].includes(info.docType)) { title = `${info.buyer}: ${doc_type_name} ${info.number}`; subject = `${doc_type_name} ${info.number} issued by ${info.buyer} on ${date}`; doc_x = `Order-X`; author = info.buyer; } else { title = `${info.seller}: ${doc_type_name} ${info.number}`; subject = `${doc_type_name} ${info.number} dated ${date} issued by ${info.seller}`; doc_x = `Factur-X`; author = info.seller; } return { title, subject, author, keywords: [doc_type_name, doc_x], date: info.date }; } function setPDFA3BMetadata({ date, documentId, title, subject, author, producer, creator, filename, conformanceLevel }, pdf) { const metadataXML = ` <?xpacket begin="" id="${documentId}"?> <x:xmpmeta xmlns:x="adobe:ns:meta/"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/"> <pdfaid:part>3</pdfaid:part> <pdfaid:conformance>B</pdfaid:conformance> </rdf:Description> <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"> <dc:creator> <rdf:Seq> <rdf:li>${author}</rdf:li> </rdf:Seq> </dc:creator> <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">${title}</rdf:li> </rdf:Alt> </dc:title> <dc:description> <rdf:Alt> <rdf:li xml:lang="x-default">${subject}</rdf:li> </rdf:Alt> </dc:description> </rdf:Description> <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/"> <pdf:Producer>${producer}</pdf:Producer> </rdf:Description> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/"> <xmp:CreatorTool>${creator}</xmp:CreatorTool> <xmp:CreateDate>${formatDateMetadata({ date })}</xmp:CreateDate> <xmp:ModifyDate>${formatDateMetadata({ date })}</xmp:ModifyDate> <xmp:MetadataDate>${formatDateMetadata({ date })}</xmp:MetadataDate> </rdf:Description> <rdf:Description xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#" rdf:about=""> <pdfaExtension:schemas> <rdf:Bag> <rdf:li rdf:parseType="Resource"> <pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema> <pdfaSchema:namespaceURI>urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#</pdfaSchema:namespaceURI> <pdfaSchema:prefix>fx</pdfaSchema:prefix> <pdfaSchema:property> <rdf:Seq> <rdf:li rdf:parseType="Resource"> <pdfaProperty:name>DocumentFileName</pdfaProperty:name> <pdfaProperty:valueType>Text</pdfaProperty:valueType> <pdfaProperty:category>external</pdfaProperty:category> <pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description> </rdf:li> <rdf:li rdf:parseType="Resource"> <pdfaProperty:name>DocumentType</pdfaProperty:name> <pdfaProperty:valueType>Text</pdfaProperty:valueType> <pdfaProperty:category>external</pdfaProperty:category> <pdfaProperty:description>INVOICE</pdfaProperty:description> </rdf:li> <rdf:li rdf:parseType="Resource"> <pdfaProperty:name>Version</pdfaProperty:name> <pdfaProperty:valueType>Text</pdfaProperty:valueType> <pdfaProperty:category>external</pdfaProperty:category> <pdfaProperty:description>The actual version of the Factur-X XML schema</pdfaProperty:description> </rdf:li> <rdf:li rdf:parseType="Resource"> <pdfaProperty:name>ConformanceLevel</pdfaProperty:name> <pdfaProperty:valueType>Text</pdfaProperty:valueType> <pdfaProperty:category>external</pdfaProperty:category> <pdfaProperty:description>The conformance level of the embedded Factur-X data</pdfaProperty:description> </rdf:li> </rdf:Seq> </pdfaSchema:property> </rdf:li> </rdf:Bag> </pdfaExtension:schemas> </rdf:Description> <rdf:Description xmlns:fx="urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#" rdf:about=""> <fx:DocumentType>INVOICE</fx:DocumentType> <fx:DocumentFileName>${filename}</fx:DocumentFileName> <fx:Version>1.0</fx:Version> <fx:ConformanceLevel>${conformanceLevel}</fx:ConformanceLevel> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?> `.trim(); const metadataStream = pdf.context.stream(metadataXML, { Type: "Metadata", Subtype: "XML", Length: metadataXML.length }); const metadataStreamRef = pdf.context.register(metadataStream); pdf.catalog.set(PDFName.of("Metadata"), metadataStreamRef); } function formatDateMetadata({ date }) { return `${date.toISOString().split(".")[0]}Z`; } async function generate(options) { const xml = await resolveXml(options.xml); const flavor = options.flavor || getFlavor(xml); const level = options.level || getLevel(xml); if (options.check === true) { const result = await check({ xml, flavor, level }); if (!result.valid) { throw new Error(`Invalid XML format (${flavor} - ${level})`); } } let meta = options.meta; if (!meta) { const info = await extractBaseInfo(xml); meta = baseInfo2PdfMetadata(info); } meta.date ||= /* @__PURE__ */ new Date(); let description = ""; let filename = ""; let conformanceLevel = ""; switch (flavor) { case "facturx": filename = FACTURX_FILENAME; description = "Factur-X XML file"; conformanceLevel = FACTURX_CONFORMANCE_LEVEL[level] || ""; break; case "orderx": filename = ORDERX_FILENAME; description = "Order-X XML file"; break; case "zugferd": filename = ZUGFERD_FILENAMES[0]; description = "ZUGFeRD XML file"; break; default: throw new Error(`Unknown schema flavor: "${options.flavor}"`); } const pdf = await resolvePdf(options.pdf); let documentId; if (pdf.context.trailerInfo.ID) { throw new Error("Not implemented yet: Document ID already set"); } else { documentId = randomBytes(16).toString("hex"); const id = PDFHexString.of(documentId); pdf.context.trailerInfo.ID = pdf.context.obj([id, id]); } const encoder = new TextEncoder(); const uint8Array = encoder.encode(xml.toString()); await pdf.attach(uint8Array, filename, { afRelationship: AFRelationship.Data, mimeType: "text/xml", creationDate: meta.date, modificationDate: meta.date, description }); const creator = `${pkg.name} npm lib v${pkg.version} (https://github.com/${pkg.repository})`; if (options.language) { pdf.setLanguage(options.language); } pdf.setCreationDate(meta.date); pdf.setModificationDate(meta.date); pdf.setTitle(meta.title); pdf.setSubject(meta.subject); pdf.setAuthor(meta.author); pdf.setKeywords(meta.keywords); pdf.setCreator(creator); setPDFA3BMetadata({ ...meta, documentId, filename, conformanceLevel, producer: creator, creator }, pdf); return await pdf.save(); } export { generate as g };