lbx-invoice
Version:
Provides functionality around generating invoices.
157 lines (144 loc) • 9.01 kB
text/typescript
/* 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;
}
}