UNPKG

lbx-invoice

Version:

Provides functionality around generating invoices.

157 lines (144 loc) 9.01 kB
/* eslint-disable stylistic/max-len */ import { AFRelationship } from 'pdf-lib'; import { PdfUtilities } from './pdf.utilities'; import { XML, XmlUtilities } from './xml.utilities'; import { BaseCompanyInfo, BaseInvoice } from '../models'; /** * Provides functionality for handling factur-x conformance. */ export abstract class FacturXUtilities { /** * Creates and attaches a factur-x xml to the invoice pdf at the given path. * @param pdfFilePath - The path of the invoice pdf to attach to. * @param invoice - The invoice data to generate the xml from. * @param currency - The currency code, eg. 'USD'. * @param companyInfo - Info about the seller. * @param tradeContact - The trade contact. Defaults to companyInfo.ceo. */ static async attachFacturX<Invoice extends BaseInvoice>( pdfFilePath: string, invoice: Invoice, currency: string, companyInfo: BaseCompanyInfo, tradeContact: string | undefined = companyInfo.ceo ): Promise<void> { const xmlBytes: Uint8Array = this.createFacturXBytes(invoice, currency, companyInfo, tradeContact); const now: Date = new Date(); await PdfUtilities.attach(pdfFilePath, xmlBytes, 'factur-x.xml', { mimeType: 'text/xml', afRelationship: AFRelationship.Alternative, creationDate: now, modificationDate: now }); } /** * Adds the required factur-x metadata to the pdf file at the provided path. * @param pdfFilePath - The path of the invoice pdf. * @param author - The author that should be set. * @param id - The id to use in the metadata. * @param producer - The producer that should be set. Defaults to `lbx-invoice`. */ static async addFacturXMetadata(pdfFilePath: string, author: string, id: string, producer: string = 'lbx-invoice'): Promise<void> { const invoiceNumber: string = pdfFilePath.split('/')[pdfFilePath.split('/').length]; const now: Date = new Date(); const whitespacePadding: string = ''; // <dc:subject> // <rdf:Bag> // ${options.keywords.map(keyword => `<rdf:li>${keyword}</rdf:li>`).join('\n')} // </rdf:Bag> // </dc:subject> // <xmp:CreatorTool>${options.creatorTool}</xmp:CreatorTool> const metadataXML: string = ` <?xpacket begin="" id="${id}"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.2-c001 63.139439, 2010/09/27-13:37:26 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"> <dc:format>application/pdf</dc:format> <dc:creator> <rdf:Seq> <rdf:li>${author}</rdf:li> </rdf:Seq> </dc:creator> <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">${invoiceNumber}</rdf:li> </rdf:Alt> </dc:title> <!--Keywords--> </rdf:Description> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/"> <!--Creator Tool--> <xmp:CreateDate>${now.toISOString()}</xmp:CreateDate> <xmp:ModifyDate>${now.toISOString()}</xmp:ModifyDate> <xmp:MetadataDate>${now.toISOString()}</xmp:MetadataDate> </rdf:Description> <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/"> <pdf:Producer>${producer}</pdf:Producer> </rdf:Description> <!-- The actual Factur-X properties; adjust if required --> <rdf:Description rdf:about="" xmlns:fx="urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#"> <fx:ConformanceLevel>BASIC</fx:ConformanceLevel> <fx:DocumentFileName>factur-x.xml</fx:DocumentFileName> <fx:DocumentType>INVOICE</fx:DocumentType> <fx:Version>1.0</fx:Version> </rdf:Description> <!-- PDF/A extension schema description for the Factur-X schema. It is crucial for PDF/A-3 conformance. Don't touch! --> <rdf:Description rdf:about="" 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#"> <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:RDF> </x:xmpmeta> ${whitespacePadding} <?xpacket end="w"?> `.trim(); // TODO: enable await PdfUtilities['setXmpMetadata'](metadataXML, pdfFilePath); } private static createFacturXBytes( invoice: BaseInvoice, currency: string, companyInfo: BaseCompanyInfo, tradeContact: string | undefined = companyInfo.ceo ): Uint8Array { const xml: XML = XmlUtilities.createCrossIndustryInvoiceXml(invoice, currency, companyInfo, tradeContact); const xmlString: string = xml.end({ prettyPrint: true }); const xmlBytes: Uint8Array = new TextEncoder().encode(xmlString); return xmlBytes; } }