diff --git a/examples/pdf-handling.ts b/examples/pdf-handling.ts deleted file mode 100644 index fc18248..0000000 --- a/examples/pdf-handling.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { PDFEmbedder, PDFExtractor, TInvoice, FacturXEncoder } from '../ts/index.js'; -import * as fs from 'fs/promises'; - -/** - * Example demonstrating how to use the PDF handling classes - */ -async function pdfHandlingExample() { - try { - // Create a sample invoice - const invoice: TInvoice = createSampleInvoice(); - - // Create a Factur-X encoder - const encoder = new FacturXEncoder(); - - // Generate XML - const xmlContent = await encoder.encode(invoice); - console.log('Generated XML:'); - console.log(xmlContent.substring(0, 500) + '...'); - - // Load a sample PDF - const pdfBuffer = await fs.readFile('examples/sample.pdf'); - console.log(`Loaded PDF (${pdfBuffer.length} bytes)`); - - // Create a PDF embedder - const embedder = new PDFEmbedder(); - - // Embed XML into PDF - const modifiedPdfBuffer = await embedder.embedXml( - pdfBuffer, - xmlContent, - 'factur-x.xml', - 'Factur-X XML Invoice' - ); - console.log(`Created modified PDF (${modifiedPdfBuffer.length} bytes)`); - - // Save the modified PDF - await fs.writeFile('examples/output.pdf', modifiedPdfBuffer); - console.log('Saved modified PDF to examples/output.pdf'); - - // Create a PDF extractor - const extractor = new PDFExtractor(); - - // Extract XML from the modified PDF - const extractedXml = await extractor.extractXml(modifiedPdfBuffer); - console.log('Extracted XML:'); - console.log(extractedXml ? extractedXml.substring(0, 500) + '...' : 'No XML found'); - - // Save the extracted XML - if (extractedXml) { - await fs.writeFile('examples/extracted.xml', extractedXml); - console.log('Saved extracted XML to examples/extracted.xml'); - } - - console.log('PDF handling example completed successfully'); - } catch (error) { - console.error('Error in PDF handling example:', error); - } -} - -/** - * Creates a sample invoice for testing - * @returns Sample invoice - */ -function createSampleInvoice(): TInvoice { - return { - type: 'invoice', - id: 'INV-2023-001', - invoiceType: 'debitnote', - date: Date.now(), - status: 'invoice', - versionInfo: { - type: 'final', - version: '1.0.0' - }, - language: 'en', - incidenceId: 'INV-2023-001', - from: { - type: 'company', - name: 'Supplier Company', - description: 'Supplier', - address: { - streetName: 'Supplier Street', - houseNumber: '123', - postalCode: '12345', - city: 'Supplier City', - country: 'DE', - countryCode: 'DE' - }, - status: 'active', - foundedDate: { - year: 2000, - month: 1, - day: 1 - }, - registrationDetails: { - vatId: 'DE123456789', - registrationId: 'HRB12345', - registrationName: 'Supplier Company GmbH' - } - }, - to: { - type: 'company', - name: 'Customer Company', - description: 'Customer', - address: { - streetName: 'Customer Street', - houseNumber: '456', - postalCode: '54321', - city: 'Customer City', - country: 'DE', - countryCode: 'DE' - }, - status: 'active', - foundedDate: { - year: 2005, - month: 6, - day: 15 - }, - registrationDetails: { - vatId: 'DE987654321', - registrationId: 'HRB54321', - registrationName: 'Customer Company GmbH' - } - }, - subject: 'Invoice INV-2023-001', - content: { - invoiceData: { - id: 'INV-2023-001', - status: null, - type: 'debitnote', - billedBy: { - type: 'company', - name: 'Supplier Company', - description: 'Supplier', - address: { - streetName: 'Supplier Street', - houseNumber: '123', - postalCode: '12345', - city: 'Supplier City', - country: 'DE', - countryCode: 'DE' - }, - status: 'active', - foundedDate: { - year: 2000, - month: 1, - day: 1 - }, - registrationDetails: { - vatId: 'DE123456789', - registrationId: 'HRB12345', - registrationName: 'Supplier Company GmbH' - } - }, - billedTo: { - type: 'company', - name: 'Customer Company', - description: 'Customer', - address: { - streetName: 'Customer Street', - houseNumber: '456', - postalCode: '54321', - city: 'Customer City', - country: 'DE', - countryCode: 'DE' - }, - status: 'active', - foundedDate: { - year: 2005, - month: 6, - day: 15 - }, - registrationDetails: { - vatId: 'DE987654321', - registrationId: 'HRB54321', - registrationName: 'Customer Company GmbH' - } - }, - deliveryDate: Date.now(), - dueInDays: 30, - periodOfPerformance: null, - printResult: null, - currency: 'EUR', - notes: ['Thank you for your business'], - items: [ - { - position: 1, - name: 'Product A', - articleNumber: 'PROD-A', - unitType: 'EA', - unitQuantity: 2, - unitNetPrice: 100, - vatPercentage: 19 - }, - { - position: 2, - name: 'Service B', - articleNumber: 'SERV-B', - unitType: 'HUR', - unitQuantity: 5, - unitNetPrice: 80, - vatPercentage: 19 - } - ], - reverseCharge: false - }, - textData: null, - timesheetData: null, - contractData: null - } - } as TInvoice; -} - -// Run the example -pdfHandlingExample(); diff --git a/test/output/circular-corpus-results.json b/test/output/circular-corpus-results.json new file mode 100644 index 0000000..014ba97 --- /dev/null +++ b/test/output/circular-corpus-results.json @@ -0,0 +1,45 @@ +{ + "cii": { + "success": 3, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_1_Teilrechnung.cii.xml", + "success": true, + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_2_Teilrechnung.cii.xml", + "success": true, + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_AbweichenderZahlungsempf.cii.xml", + "success": true, + "error": null + } + ] + }, + "ubl": { + "success": 3, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_1_Teilrechnung.ubl.xml", + "success": true, + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_2_Teilrechnung.ubl.xml", + "success": true, + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_AbweichenderZahlungsempf.ubl.xml", + "success": true, + "error": null + } + ] + }, + "totalSuccessRate": 1 +} \ No newline at end of file diff --git a/test/output/circular/EN16931_1_Teilrechnung.cii.xml-exported.xml b/test/output/circular/EN16931_1_Teilrechnung.cii.xml-exported.xml new file mode 100644 index 0000000..5631d5b --- /dev/null +++ b/test/output/circular/EN16931_1_Teilrechnung.cii.xml-exported.xml @@ -0,0 +1,5 @@ + + +urn:cen.eu:en16931:2017380471102NaNNaNNaNLieferant GmbHLieferantenstraße 20080333MünchenDEDE123456789201/113/40209Kunden AG MitteKundenstraße 15069876FrankfurtDEEURNaNNaNNaN180.7020.50201.20201.201Kunstrasen grün 3m breitKR3M3.333VATS1910.002SchweinesteakSFK55.501VATS75.503Mineralwasser Medium +12 x 1,0l PET + GTRWA55.4920VATS7109.804PfandPFA52.7720VATS1955.40 \ No newline at end of file diff --git a/test/output/circular/EN16931_1_Teilrechnung.ubl.xml-exported.xml b/test/output/circular/EN16931_1_Teilrechnung.ubl.xml-exported.xml new file mode 100644 index 0000000..f2dd3ef --- /dev/null +++ b/test/output/circular/EN16931_1_Teilrechnung.ubl.xml-exported.xml @@ -0,0 +1,161 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471102 + 2018-06-05 + 2018-07-05 + 380 + EUR + + + + + Lieferant GmbH + + + Lieferantenstraße 20 + 0 + München + 80333 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Kunden AG Mitte + + + Kundenstraße 15 + 0 + Frankfurt + 69876 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 3 + 9.9999 + + Kunstrasen grün 3m breit + + + KR3M + + + S + 19 + + VAT + + + + + 3.3333 + + + + 2 + 1 + 5.5 + + Schweinesteak + + + SFK5 + + + S + 7 + + VAT + + + + + 5.5 + + + + 3 + 20 + 109.80000000000001 + + Mineralwasser Medium +12 x 1,0l PET + + + + GTRWA5 + + + S + 7 + + VAT + + + + + 5.49 + + + + 4 + 20 + 55.4 + + Pfand + + + PFA5 + + + S + 19 + + VAT + + + + + 2.77 + + + \ No newline at end of file diff --git a/test/output/circular/EN16931_2_Teilrechnung.cii.xml-exported.xml b/test/output/circular/EN16931_2_Teilrechnung.cii.xml-exported.xml new file mode 100644 index 0000000..bbad2c8 --- /dev/null +++ b/test/output/circular/EN16931_2_Teilrechnung.cii.xml-exported.xml @@ -0,0 +1,3 @@ + + +urn:cen.eu:en16931:2017380471113NaNNaNNaNLieferant GmbHLieferantenstraße 20080333MünchenDEDE123456789201/113/40209Kunden AG MitteKundenstraße 15069876FrankfurtDEEURNaNNaNNaN22.001.5423.5423.541SchweinesteakSFK55.504VATS722.00 \ No newline at end of file diff --git a/test/output/circular/EN16931_2_Teilrechnung.ubl.xml-exported.xml b/test/output/circular/EN16931_2_Teilrechnung.ubl.xml-exported.xml new file mode 100644 index 0000000..70482ee --- /dev/null +++ b/test/output/circular/EN16931_2_Teilrechnung.ubl.xml-exported.xml @@ -0,0 +1,93 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471113 + 2018-06-13 + 2018-07-13 + 380 + EUR + + + + + Lieferant GmbH + + + Lieferantenstraße 20 + 0 + München + 80333 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Kunden AG Mitte + + + Kundenstraße 15 + 0 + Frankfurt + 69876 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 4 + 22 + + Schweinesteak + + + SFK5 + + + S + 7 + + VAT + + + + + 5.5 + + + \ No newline at end of file diff --git a/test/output/circular/EN16931_AbweichenderZahlungsempf.cii.xml-exported.xml b/test/output/circular/EN16931_AbweichenderZahlungsempf.cii.xml-exported.xml new file mode 100644 index 0000000..58d413a --- /dev/null +++ b/test/output/circular/EN16931_AbweichenderZahlungsempf.cii.xml-exported.xml @@ -0,0 +1,3 @@ + + +urn:cen.eu:en16931:2017380471102NaNNaNNaNLieferant GmbHLieferantenstraße 20080333MünchenDEDE123456789201/113/40209Kunden AG MitteKundenstraße 15069876FrankfurtDEEURNaNNaNNaN473.0056.87529.87529.871Trennblätter A4TB100A49.9020VATS19198.002Joghurt BananeARNR25.5050VATS7275.00 \ No newline at end of file diff --git a/test/output/circular/EN16931_AbweichenderZahlungsempf.ubl.xml-exported.xml b/test/output/circular/EN16931_AbweichenderZahlungsempf.ubl.xml-exported.xml new file mode 100644 index 0000000..9f5ef56 --- /dev/null +++ b/test/output/circular/EN16931_AbweichenderZahlungsempf.ubl.xml-exported.xml @@ -0,0 +1,115 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471102 + 2018-03-05 + 2018-04-04 + 380 + EUR + + + + + Lieferant GmbH + + + Lieferantenstraße 20 + 0 + München + 80333 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Kunden AG Mitte + + + Kundenstraße 15 + 0 + Frankfurt + 69876 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 20 + 198 + + Trennblätter A4 + + + TB100A4 + + + S + 19 + + VAT + + + + + 9.9 + + + + 2 + 50 + 275 + + Joghurt Banane + + + ARNR2 + + + S + 7 + + VAT + + + + + 5.5 + + + \ No newline at end of file diff --git a/test/output/focused/EN16931_1_Teilrechnung.cii.xml-exported.xml b/test/output/focused/EN16931_1_Teilrechnung.cii.xml-exported.xml new file mode 100644 index 0000000..5631d5b --- /dev/null +++ b/test/output/focused/EN16931_1_Teilrechnung.cii.xml-exported.xml @@ -0,0 +1,5 @@ + + +urn:cen.eu:en16931:2017380471102NaNNaNNaNLieferant GmbHLieferantenstraße 20080333MünchenDEDE123456789201/113/40209Kunden AG MitteKundenstraße 15069876FrankfurtDEEURNaNNaNNaN180.7020.50201.20201.201Kunstrasen grün 3m breitKR3M3.333VATS1910.002SchweinesteakSFK55.501VATS75.503Mineralwasser Medium +12 x 1,0l PET + GTRWA55.4920VATS7109.804PfandPFA52.7720VATS1955.40 \ No newline at end of file diff --git a/test/output/focused/EN16931_1_Teilrechnung.ubl.xml-exported.xml b/test/output/focused/EN16931_1_Teilrechnung.ubl.xml-exported.xml new file mode 100644 index 0000000..f2dd3ef --- /dev/null +++ b/test/output/focused/EN16931_1_Teilrechnung.ubl.xml-exported.xml @@ -0,0 +1,161 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471102 + 2018-06-05 + 2018-07-05 + 380 + EUR + + + + + Lieferant GmbH + + + Lieferantenstraße 20 + 0 + München + 80333 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Kunden AG Mitte + + + Kundenstraße 15 + 0 + Frankfurt + 69876 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 3 + 9.9999 + + Kunstrasen grün 3m breit + + + KR3M + + + S + 19 + + VAT + + + + + 3.3333 + + + + 2 + 1 + 5.5 + + Schweinesteak + + + SFK5 + + + S + 7 + + VAT + + + + + 5.5 + + + + 3 + 20 + 109.80000000000001 + + Mineralwasser Medium +12 x 1,0l PET + + + + GTRWA5 + + + S + 7 + + VAT + + + + + 5.49 + + + + 4 + 20 + 55.4 + + Pfand + + + PFA5 + + + S + 19 + + VAT + + + + + 2.77 + + + \ No newline at end of file diff --git a/test/output/focused/EN16931_2_Teilrechnung.cii.xml-exported.xml b/test/output/focused/EN16931_2_Teilrechnung.cii.xml-exported.xml new file mode 100644 index 0000000..bbad2c8 --- /dev/null +++ b/test/output/focused/EN16931_2_Teilrechnung.cii.xml-exported.xml @@ -0,0 +1,3 @@ + + +urn:cen.eu:en16931:2017380471113NaNNaNNaNLieferant GmbHLieferantenstraße 20080333MünchenDEDE123456789201/113/40209Kunden AG MitteKundenstraße 15069876FrankfurtDEEURNaNNaNNaN22.001.5423.5423.541SchweinesteakSFK55.504VATS722.00 \ No newline at end of file diff --git a/test/output/focused/EN16931_2_Teilrechnung.ubl.xml-exported.xml b/test/output/focused/EN16931_2_Teilrechnung.ubl.xml-exported.xml new file mode 100644 index 0000000..70482ee --- /dev/null +++ b/test/output/focused/EN16931_2_Teilrechnung.ubl.xml-exported.xml @@ -0,0 +1,93 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471113 + 2018-06-13 + 2018-07-13 + 380 + EUR + + + + + Lieferant GmbH + + + Lieferantenstraße 20 + 0 + München + 80333 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Kunden AG Mitte + + + Kundenstraße 15 + 0 + Frankfurt + 69876 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 4 + 22 + + Schweinesteak + + + SFK5 + + + S + 7 + + VAT + + + + + 5.5 + + + \ No newline at end of file diff --git a/test/output/focused/EN16931_AbweichenderZahlungsempf.cii.xml-exported.xml b/test/output/focused/EN16931_AbweichenderZahlungsempf.cii.xml-exported.xml new file mode 100644 index 0000000..58d413a --- /dev/null +++ b/test/output/focused/EN16931_AbweichenderZahlungsempf.cii.xml-exported.xml @@ -0,0 +1,3 @@ + + +urn:cen.eu:en16931:2017380471102NaNNaNNaNLieferant GmbHLieferantenstraße 20080333MünchenDEDE123456789201/113/40209Kunden AG MitteKundenstraße 15069876FrankfurtDEEURNaNNaNNaN473.0056.87529.87529.871Trennblätter A4TB100A49.9020VATS19198.002Joghurt BananeARNR25.5050VATS7275.00 \ No newline at end of file diff --git a/test/output/focused/EN16931_AbweichenderZahlungsempf.ubl.xml-exported.xml b/test/output/focused/EN16931_AbweichenderZahlungsempf.ubl.xml-exported.xml new file mode 100644 index 0000000..9f5ef56 --- /dev/null +++ b/test/output/focused/EN16931_AbweichenderZahlungsempf.ubl.xml-exported.xml @@ -0,0 +1,115 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471102 + 2018-03-05 + 2018-04-04 + 380 + EUR + + + + + Lieferant GmbH + + + Lieferantenstraße 20 + 0 + München + 80333 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Kunden AG Mitte + + + Kundenstraße 15 + 0 + Frankfurt + 69876 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 20 + 198 + + Trennblätter A4 + + + TB100A4 + + + S + 19 + + VAT + + + + + 9.9 + + + + 2 + 50 + 275 + + Joghurt Banane + + + ARNR2 + + + S + 7 + + VAT + + + + + 5.5 + + + \ No newline at end of file diff --git a/test/output/focused/EN16931_Betriebskostenabrechnung.cii.xml-exported.xml b/test/output/focused/EN16931_Betriebskostenabrechnung.cii.xml-exported.xml new file mode 100644 index 0000000..b824ab7 --- /dev/null +++ b/test/output/focused/EN16931_Betriebskostenabrechnung.cii.xml-exported.xml @@ -0,0 +1,3 @@ + + +urn:cen.eu:en16931:2017380471102NaNNaNNaNGrundbesitz GmbH & Co.Musterstraße 42075645FrankfurtDEDE136695976201/113/40209Beispielmieter GmbHVerwaltung Straße 40012345MusterstadtDEEURNaNNaNNaN15387.082923.5518310.6318310.631Abrechnungskreis 115387.081VATS1915387.08 \ No newline at end of file diff --git a/test/output/focused/EN16931_Betriebskostenabrechnung.ubl.xml-exported.xml b/test/output/focused/EN16931_Betriebskostenabrechnung.ubl.xml-exported.xml new file mode 100644 index 0000000..3a3f113 --- /dev/null +++ b/test/output/focused/EN16931_Betriebskostenabrechnung.ubl.xml-exported.xml @@ -0,0 +1,90 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471102 + 2018-03-05 + 2018-04-04 + 380 + EUR + + + + + Grundbesitz GmbH & Co. + + + Musterstraße 42 + 0 + Frankfurt + 75645 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Beispielmieter GmbH + + + Verwaltung Straße 40 + 0 + Musterstadt + 12345 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 1 + 15387.08 + + Abrechnungskreis 1 + + + S + 19 + + VAT + + + + + 15387.08 + + + \ No newline at end of file diff --git a/test/output/focused/EN16931_Einfach.cii.xml-exported.xml b/test/output/focused/EN16931_Einfach.cii.xml-exported.xml new file mode 100644 index 0000000..58d413a --- /dev/null +++ b/test/output/focused/EN16931_Einfach.cii.xml-exported.xml @@ -0,0 +1,3 @@ + + +urn:cen.eu:en16931:2017380471102NaNNaNNaNLieferant GmbHLieferantenstraße 20080333MünchenDEDE123456789201/113/40209Kunden AG MitteKundenstraße 15069876FrankfurtDEEURNaNNaNNaN473.0056.87529.87529.871Trennblätter A4TB100A49.9020VATS19198.002Joghurt BananeARNR25.5050VATS7275.00 \ No newline at end of file diff --git a/test/output/focused/EN16931_Einfach.ubl.xml-exported.xml b/test/output/focused/EN16931_Einfach.ubl.xml-exported.xml new file mode 100644 index 0000000..9f5ef56 --- /dev/null +++ b/test/output/focused/EN16931_Einfach.ubl.xml-exported.xml @@ -0,0 +1,115 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471102 + 2018-03-05 + 2018-04-04 + 380 + EUR + + + + + Lieferant GmbH + + + Lieferantenstraße 20 + 0 + München + 80333 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Kunden AG Mitte + + + Kundenstraße 15 + 0 + Frankfurt + 69876 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 20 + 198 + + Trennblätter A4 + + + TB100A4 + + + S + 19 + + VAT + + + + + 9.9 + + + + 2 + 50 + 275 + + Joghurt Banane + + + ARNR2 + + + S + 7 + + VAT + + + + + 5.5 + + + \ No newline at end of file diff --git a/test/output/focused/zugferd_2p0_EN16931_1_Teilrechnung.pdf-extracted.xml b/test/output/focused/zugferd_2p0_EN16931_1_Teilrechnung.pdf-extracted.xml new file mode 100644 index 0000000..d08f64c Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_1_Teilrechnung.pdf-extracted.xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_1_Teilrechnung.pdf-raw-(zugferd-invoice.xml).xml b/test/output/focused/zugferd_2p0_EN16931_1_Teilrechnung.pdf-raw-(zugferd-invoice.xml).xml new file mode 100644 index 0000000..d08f64c Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_1_Teilrechnung.pdf-raw-(zugferd-invoice.xml).xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_2_Teilrechnung.pdf-extracted.xml b/test/output/focused/zugferd_2p0_EN16931_2_Teilrechnung.pdf-extracted.xml new file mode 100644 index 0000000..e00bb1d Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_2_Teilrechnung.pdf-extracted.xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_2_Teilrechnung.pdf-raw-(zugferd-invoice.xml).xml b/test/output/focused/zugferd_2p0_EN16931_2_Teilrechnung.pdf-raw-(zugferd-invoice.xml).xml new file mode 100644 index 0000000..e00bb1d Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_2_Teilrechnung.pdf-raw-(zugferd-invoice.xml).xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_AbweichenderZahlungsempf.pdf-extracted.xml b/test/output/focused/zugferd_2p0_EN16931_AbweichenderZahlungsempf.pdf-extracted.xml new file mode 100644 index 0000000..9be9e0d Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_AbweichenderZahlungsempf.pdf-extracted.xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_AbweichenderZahlungsempf.pdf-raw-(zugferd-invoice.xml).xml b/test/output/focused/zugferd_2p0_EN16931_AbweichenderZahlungsempf.pdf-raw-(zugferd-invoice.xml).xml new file mode 100644 index 0000000..9be9e0d Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_AbweichenderZahlungsempf.pdf-raw-(zugferd-invoice.xml).xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-extracted.xml b/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-extracted.xml new file mode 100644 index 0000000..8310771 Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-extracted.xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-raw-(AWV_13_Betriebskosten.pdf).xml b/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-raw-(AWV_13_Betriebskosten.pdf).xml new file mode 100644 index 0000000..73d03a6 Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-raw-(AWV_13_Betriebskosten.pdf).xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-raw-(zugferd-invoice.xml).xml b/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-raw-(zugferd-invoice.xml).xml new file mode 100644 index 0000000..8310771 Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf-raw-(zugferd-invoice.xml).xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_Einfach.pdf-extracted.xml b/test/output/focused/zugferd_2p0_EN16931_Einfach.pdf-extracted.xml new file mode 100644 index 0000000..b94f87c Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_Einfach.pdf-extracted.xml differ diff --git a/test/output/focused/zugferd_2p0_EN16931_Einfach.pdf-raw-(zugferd-invoice.xml).xml b/test/output/focused/zugferd_2p0_EN16931_Einfach.pdf-raw-(zugferd-invoice.xml).xml new file mode 100644 index 0000000..b94f87c Binary files /dev/null and b/test/output/focused/zugferd_2p0_EN16931_Einfach.pdf-raw-(zugferd-invoice.xml).xml differ diff --git a/test/output/simple/EN16931_Einfach.cii.xml-exported.xml b/test/output/simple/EN16931_Einfach.cii.xml-exported.xml new file mode 100644 index 0000000..58d413a --- /dev/null +++ b/test/output/simple/EN16931_Einfach.cii.xml-exported.xml @@ -0,0 +1,3 @@ + + +urn:cen.eu:en16931:2017380471102NaNNaNNaNLieferant GmbHLieferantenstraße 20080333MünchenDEDE123456789201/113/40209Kunden AG MitteKundenstraße 15069876FrankfurtDEEURNaNNaNNaN473.0056.87529.87529.871Trennblätter A4TB100A49.9020VATS19198.002Joghurt BananeARNR25.5050VATS7275.00 \ No newline at end of file diff --git a/test/output/simple/EN16931_Einfach.ubl.xml-exported.xml b/test/output/simple/EN16931_Einfach.ubl.xml-exported.xml new file mode 100644 index 0000000..9f5ef56 --- /dev/null +++ b/test/output/simple/EN16931_Einfach.ubl.xml-exported.xml @@ -0,0 +1,115 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0 + 471102 + 2018-03-05 + 2018-04-04 + 380 + EUR + + + + + Lieferant GmbH + + + Lieferantenstraße 20 + 0 + München + 80333 + + DE + + + + + 201/113/40209 + + VAT + + + + + + + + + + Kunden AG Mitte + + + Kundenstraße 15 + 0 + Frankfurt + 69876 + + DE + + + + + + + + Due in 30 days + + + + 0.00 + + + + 0.00 + 0.00 + 0.00 + 0.00 + + + + + 1 + 20 + 198 + + Trennblätter A4 + + + TB100A4 + + + S + 19 + + VAT + + + + + 9.9 + + + + 2 + 50 + 275 + + Joghurt Banane + + + ARNR2 + + + S + 7 + + VAT + + + + + 5.5 + + + \ No newline at end of file diff --git a/test/output/test-invoice-with-xml.pdf b/test/output/test-invoice-with-xml.pdf index a8dad82..b5337ef 100644 Binary files a/test/output/test-invoice-with-xml.pdf and b/test/output/test-invoice-with-xml.pdf differ diff --git a/test/output/validation-corpus-results.json b/test/output/validation-corpus-results.json new file mode 100644 index 0000000..db7272a --- /dev/null +++ b/test/output/validation-corpus-results.json @@ -0,0 +1,192 @@ +{ + "zugferdV2Correct": { + "success": 5, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Avoir_FR_type381_BASIC.pdf", + "success": true, + "valid": true, + "errors": [], + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_DOM_BASICWL.pdf", + "success": true, + "valid": true, + "errors": [], + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_DOM_MINIMUM.pdf", + "success": true, + "valid": true, + "errors": [], + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_FR_BASICWL.pdf", + "success": true, + "valid": true, + "errors": [], + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_FR_MINIMUM.pdf", + "success": true, + "valid": true, + "errors": [], + "error": null + } + ] + }, + "zugferdV2Fail": { + "success": 0, + "fail": 5, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type380_BASIC.pdf", + "success": false, + "valid": true, + "errors": [], + "error": "Validation result (true) doesn't match expectation (false)" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type380_EN16931.pdf", + "success": false, + "valid": null, + "errors": null, + "error": "Error: No XML found in PDF" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type380_MINIMUM.pdf", + "success": false, + "valid": true, + "errors": [], + "error": "Validation result (true) doesn't match expectation (false)" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type381_BASICWL.pdf", + "success": false, + "valid": true, + "errors": [], + "error": "Validation result (true) doesn't match expectation (false)" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type381_EN16931.pdf", + "success": false, + "valid": null, + "errors": null, + "error": "Error: No XML found in PDF" + } + ] + }, + "cii": { + "success": 5, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_1_Teilrechnung.cii.xml", + "success": true, + "valid": true, + "errors": [], + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_2_Teilrechnung.cii.xml", + "success": true, + "valid": true, + "errors": [], + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_AbweichenderZahlungsempf.cii.xml", + "success": true, + "valid": true, + "errors": [], + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Betriebskostenabrechnung.cii.xml", + "success": true, + "valid": true, + "errors": [], + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Einfach.cii.xml", + "success": true, + "valid": true, + "errors": [], + "error": null + } + ] + }, + "ubl": { + "success": 0, + "fail": 5, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_1_Teilrechnung.ubl.xml", + "success": false, + "valid": false, + "errors": [ + { + "code": "VAL-ERROR", + "message": "Validation error: XRechnung validator not yet implemented" + } + ], + "error": "Validation result (false) doesn't match expectation (true)" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_2_Teilrechnung.ubl.xml", + "success": false, + "valid": false, + "errors": [ + { + "code": "VAL-ERROR", + "message": "Validation error: XRechnung validator not yet implemented" + } + ], + "error": "Validation result (false) doesn't match expectation (true)" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_AbweichenderZahlungsempf.ubl.xml", + "success": false, + "valid": false, + "errors": [ + { + "code": "VAL-ERROR", + "message": "Validation error: XRechnung validator not yet implemented" + } + ], + "error": "Validation result (false) doesn't match expectation (true)" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Betriebskostenabrechnung.ubl.xml", + "success": false, + "valid": false, + "errors": [ + { + "code": "VAL-ERROR", + "message": "Validation error: XRechnung validator not yet implemented" + } + ], + "error": "Validation result (false) doesn't match expectation (true)" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Einfach.ubl.xml", + "success": false, + "valid": false, + "errors": [ + { + "code": "VAL-ERROR", + "message": "Validation error: XRechnung validator not yet implemented" + } + ], + "error": "Validation result (false) doesn't match expectation (true)" + } + ] + }, + "totalCorrectSuccessRate": 0.6666666666666666 +} \ No newline at end of file diff --git a/test/output/xml-rechnung-corpus-results.json b/test/output/xml-rechnung-corpus-results.json new file mode 100644 index 0000000..1d139a4 --- /dev/null +++ b/test/output/xml-rechnung-corpus-results.json @@ -0,0 +1,350 @@ +{ + "cii": { + "success": 27, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_1_Teilrechnung.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_2_Teilrechnung.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_AbweichenderZahlungsempf.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Betriebskostenabrechnung.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Einfach.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Einfach_DueDate.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Einfach_negativePaymentDue.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Elektron.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_ElektronischeAdresse.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Gutschrift.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Haftpflichtversicherung_Versicherungssteuer.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Innergemeinschaftliche_Lieferungen.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Kraftfahrversicherung_Bruttopreise.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Miete.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_OEPNV.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Physiotherapeut.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Rabatte.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_RechnungsUebertragung.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Rechnungskorrektur.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Reisekostenabrechnung.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_SEPA_Prenotification.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/EN16931_Sachversicherung_berechneter_Steuersatz.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/XRECHNUNG_Betriebskostenabrechnung.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/XRECHNUNG_Einfach.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/XRECHNUNG_Elektron.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/XRECHNUNG_Reisekostenabrechnung.cii.xml", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/CII/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml", + "success": true, + "format": "facturx", + "error": null + } + ] + }, + "ubl": { + "success": 28, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_1_Teilrechnung.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_2_Teilrechnung.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_AbweichenderZahlungsempf.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Betriebskostenabrechnung.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Einfach.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Einfach_DueDate.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Einfach_negativePaymentDue.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Elektron.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_ElektronischeAdresse.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Gutschrift.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Haftpflichtversicherung_Versicherungssteuer.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Innergemeinschaftliche_Lieferungen.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Kraftfahrversicherung_Bruttopreise.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Miete.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_OEPNV.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Physiotherapeut.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Rabatte.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_RechnungsUebertragung.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Rechnungskorrektur.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Reisekostenabrechnung.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_SEPA_Prenotification.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/EN16931_Sachversicherung_berechneter_Steuersatz.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/XRECHNUNG_Betriebskostenabrechnung.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/XRECHNUNG_Einfach.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/XRECHNUNG_Elektron.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/XRECHNUNG_Reisekostenabrechnung.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.ubl.xml", + "success": true, + "format": "xrechnung", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/XML-Rechnung/UBL/ubl-tc434-creditnote1.xml", + "success": true, + "format": "xrechnung", + "error": null + } + ] + }, + "fx": { + "success": 0, + "fail": 0, + "details": [] + }, + "totalSuccessRate": 1 +} \ No newline at end of file diff --git a/test/output/zugferd-corpus-results.json b/test/output/zugferd-corpus-results.json new file mode 100644 index 0000000..77949f0 --- /dev/null +++ b/test/output/zugferd-corpus-results.json @@ -0,0 +1,753 @@ +{ + "zugferdV1Correct": { + "success": 19, + "fail": 2, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/4s4u/additional-data-sample-1.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_BASIC_Einfach.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_BASIC_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_COMFORT_Einfach.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_COMFORT_Haftpflichtversicherung_Versicherungssteuer.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_COMFORT_Kraftfahrversicherung_Bruttopreise.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_COMFORT_Rabatte.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_COMFORT_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_COMFORT_SEPA_Prenotification.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_COMFORT_Sachversicherung_berechneter_Steuersatz.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_EXTENDED_Kostenrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_EXTENDED_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Intarsys/ZUGFeRD_1p0_EXTENDED_Warenrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Konik/acme_invoice-42_ZUGFeRD.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Mustangproject/MustangGnuaccountingBeispielRE-20140519_499.pdf", + "success": false, + "format": null, + "error": "Error: Unsupported invoice format: unknown" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Mustangproject/MustangGnuaccountingBeispielRE-20140522_501.pdf", + "success": false, + "format": null, + "error": "Error: Unsupported invoice format: unknown" + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Mustangproject/MustangGnuaccountingBeispielRE-20140703_502.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Mustangproject/MustangGnuaccountingBeispielRE-20150613_503.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Mustangproject/MustangGnuaccountingBeispielRE-20151008_504.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Mustangproject/MustangGnuaccountingBeispielRE-20151008_504new.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/correct/Mustangproject/MustangGnuaccountingBeispielRE-20170509_505.pdf", + "success": true, + "format": "facturx", + "error": null + } + ] + }, + "zugferdV1Fail": { + "success": 3, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/fail/Mustangproject/fail1.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/fail/Mustangproject/fail2.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv1/fail/Mustangproject/fail3.pdf", + "success": true, + "format": "facturx", + "error": null + } + ] + }, + "zugferdV2Correct": { + "success": 78, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Avoir_FR_type381_BASIC.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_DOM_BASICWL.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_DOM_MINIMUM.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_FR_BASICWL.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_FR_MINIMUM.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_UE_BASICWL.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/FNFE-factur-x-examples/Facture_UE_MINIMUM.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/Mustangproject/MustangGnuaccountingBeispielRE-20190610_507.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/Mustangproject/MustangGnuaccountingBeispielRE-20201121_508.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/Mustangproject/MustangGnuaccountingBeispielRE-20201121_508_withBOM.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/PHP_@gpFacturX/sample_inofficial_20190125_atgp_factur-x_v_1_0.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/BASIC/zugferd_2p0_BASIC_Einfach.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/BASIC/zugferd_2p0_BASIC_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/BASIC/zugferd_2p0_BASIC_Taxifahrt.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_1_Teilrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_2_Teilrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_AbweichenderZahlungsempf.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Betriebskostenabrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Einfach.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Elektron.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_ElektronischeAdresse.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Gutschrift.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Haftpflichtversicherung_Versicherungssteuer.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Innergemeinschaftliche_Lieferungen.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Kraftfahrversicherung_Bruttopreise.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Miete.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_OEPNV.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Physiotherapeut.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Rabatte.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_RechnungsUebertragung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Reisekostenabrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_SEPA_Prenotification.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931/zugferd_2p0_EN16931_Sachversicherung_berechneter_Steuersatz.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EXTENDED/zugferd_2p0_EXTENDED_Fremdwaehrung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EXTENDED/zugferd_2p0_EXTENDED_InnergemeinschLieferungMehrereBestellungen.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EXTENDED/zugferd_2p0_EXTENDED_Kostenrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EXTENDED/zugferd_2p0_EXTENDED_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/EXTENDED/zugferd_2p0_EXTENDED_Warenrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/intarsys/MINIMUM/zugferd_2p0_MINIMUM.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/BASIC/zugferd_2p1_BASIC_Einfach.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/BASIC/zugferd_2p1_BASIC_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/BASIC/zugferd_2p1_BASIC_Taxifahrt.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/BASIC WL/zugferd_2p1_BASIC-WL_Buchungshilfe.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/BASIC WL/zugferd_2p1_BASIC-WL_Einfach.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_1_Teilrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_2_Teilrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_AbweichenderZahlungsempf.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Betriebskostenabrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Betriebskostenabrechnung_XRechnung_embedded.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Einfach.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Einfach_DueDate.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Einfach_negativePaymentDue.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Elektron.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Elektron_XRechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Elektron_embedded.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_ElektronischeAdresse.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Gutschrift.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Haftpflichtversicherung_Versicherungssteuer.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Innergemeinschaftliche_Lieferungen.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Kraftfahrversicherung_Bruttopreise.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Miete.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_OEPNV.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Physiotherapeut.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Rabatte.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_RechnungsUebertragung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Reisekostenabrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Reisekostenabrechnung_XRechnung_embedded.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_SEPA_Prenotification.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EN16931/zugferd_2p1_EN16931_Sachversicherung_berechneter_Steuersatz.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EXTENDED/zugferd_2p1_EXTENDED_Fremdwaehrung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EXTENDED/zugferd_2p1_EXTENDED_InnergemeinschLieferungMehrereBestellungen.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EXTENDED/zugferd_2p1_EXTENDED_Kostenrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EXTENDED/zugferd_2p1_EXTENDED_Rechnungskorrektur.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/EXTENDED/zugferd_2p1_EXTENDED_Warenrechnung.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/MINIMUM/zugferd_2p1_MINIMUM_Buchungshilfe.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/correct/symtrax/Beispiele/MINIMUM/zugferd_2p1_MINIMUM_Rechnung.pdf", + "success": true, + "format": "facturx", + "error": null + } + ] + }, + "zugferdV2Fail": { + "success": 19, + "fail": 0, + "details": [ + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type380_BASIC.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type380_EN16931.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type380_MINIMUM.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type381_BASICWL.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type381_EN16931.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Avoir_FR_type381_MINIMUM.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Facture_DOM_BASIC.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Facture_DOM_EN16931.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Facture_FR_BASIC.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Facture_FR_EN16931.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Facture_UE_BASIC.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/FNFE-factur-x-examples/Facture_UE_EN16931.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/MustangRE-20171118_506_ZUGFeRD1and2.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/Mustangproject/MustangGnuaccountingBeispielRE-20171118_506.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/Mustangproject/MustangGnuaccountingBeispielRE-20190610_507a.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/Mustangproject/MustangGnuaccountingBeispielRE-20190610_507b.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/ZUGFeRD_2.0_fully_compliant_complete.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/ZUGFeRD_2_fully_compliant_complete.pdf", + "success": true, + "format": "facturx", + "error": null + }, + { + "file": "/mnt/data/lossless/fin.cx/xinvoice/test/assets/corpus/ZUGFeRDv2/fail/python-factur-x/python-factur-x.pdf", + "success": true, + "format": "facturx", + "error": null + } + ] + }, + "totalCorrectSuccessRate": 0.9797979797979798 +} \ No newline at end of file diff --git a/test/test.circular-corpus.ts b/test/test.circular-corpus.ts new file mode 100644 index 0000000..f13ae5f --- /dev/null +++ b/test/test.circular-corpus.ts @@ -0,0 +1,165 @@ +import { tap, expect } from '@push.rocks/tapbundle'; +import { XInvoice } from '../ts/classes.xinvoice.js'; +import { InvoiceFormat } from '../ts/interfaces/common.js'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +// Test circular export/import of corpus files +tap.test('XInvoice should maintain data integrity through export/import cycle', async () => { + // Get a subset of files for circular testing + const ciiFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII'), '.xml', 3); + const ublFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL'), '.xml', 3); + + // Log the number of files found + console.log(`Found ${ciiFiles.length} CII files for circular testing`); + console.log(`Found ${ublFiles.length} UBL files for circular testing`); + + // Test CII files + const ciiResults = await testCircular(ciiFiles, 'facturx'); + console.log(`CII files circular testing: ${ciiResults.success} succeeded, ${ciiResults.fail} failed`); + + // Test UBL files + const ublResults = await testCircular(ublFiles, 'xrechnung'); + console.log(`UBL files circular testing: ${ublResults.success} succeeded, ${ublResults.fail} failed`); + + // Check that we have a reasonable success rate + const totalSuccess = ciiResults.success + ublResults.success; + const totalFiles = ciiFiles.length + ublFiles.length; + const successRate = totalSuccess / totalFiles; + + console.log(`Overall success rate for circular testing: ${(successRate * 100).toFixed(2)}%`); + + // We should have a success rate of at least 80% for circular testing + expect(successRate).toBeGreaterThan(0.8); + + // Save the test results to a file + const testDir = path.join(process.cwd(), 'test', 'output'); + await fs.mkdir(testDir, { recursive: true }); + + const testResults = { + cii: ciiResults, + ubl: ublResults, + totalSuccessRate: successRate + }; + + await fs.writeFile( + path.join(testDir, 'circular-corpus-results.json'), + JSON.stringify(testResults, null, 2) + ); +}); + +/** + * Tests circular export/import of files and returns the results + * @param files List of files to test + * @param exportFormat Format to export to + * @returns Test results + */ +async function testCircular(files: string[], exportFormat: string): Promise<{ success: number, fail: number, details: any[] }> { + const results = { + success: 0, + fail: 0, + details: [] as any[] + }; + + for (const file of files) { + try { + // Read the file + const xmlContent = await fs.readFile(file, 'utf8'); + + // Create XInvoice from XML + const xinvoice = await XInvoice.fromXml(xmlContent); + + // Export to XML + const exportedXml = await xinvoice.exportXml(exportFormat as any); + + // Create a new XInvoice from the exported XML + const reimportedXInvoice = await XInvoice.fromXml(exportedXml); + + // Check that key properties match + const keysMatch = + reimportedXInvoice.from.name === xinvoice.from.name && + reimportedXInvoice.to.name === xinvoice.to.name && + reimportedXInvoice.items.length === xinvoice.items.length; + + if (keysMatch) { + // Success + results.success++; + results.details.push({ + file, + success: true, + error: null + }); + + // Save the exported XML for inspection + const testDir = path.join(process.cwd(), 'test', 'output', 'circular'); + await fs.mkdir(testDir, { recursive: true }); + + const fileName = path.basename(file); + await fs.writeFile(path.join(testDir, `${fileName}-exported.xml`), exportedXml); + } else { + // Key properties don't match + results.fail++; + results.details.push({ + file, + success: false, + error: 'Key properties don\'t match after reimport' + }); + } + } catch (error) { + // Error processing the file + results.fail++; + results.details.push({ + file, + success: false, + error: `Error: ${error.message}` + }); + } + } + + return results; +} + +/** + * Recursively finds files with a specific extension in a directory + * @param dir Directory to search + * @param extension File extension to look for + * @param limit Maximum number of files to return + * @returns Array of file paths + */ +async function findFiles(dir: string, extension: string, limit?: number): Promise { + try { + const files = await fs.readdir(dir, { withFileTypes: true }); + + const result: string[] = []; + + for (const file of files) { + if (limit && result.length >= limit) { + break; + } + + const filePath = path.join(dir, file.name); + + if (file.isDirectory()) { + // Recursively search subdirectories + const remainingLimit = limit ? limit - result.length : undefined; + const subDirFiles = await findFiles(filePath, extension, remainingLimit); + result.push(...subDirFiles); + + if (limit && result.length >= limit) { + break; + } + } else if (file.name.toLowerCase().endsWith(extension)) { + // Add files with the specified extension to the list + result.push(filePath); + } + } + + return result; + } catch (error) { + console.error(`Error finding files in ${dir}:`, error); + return []; + } +} + +// Run the tests +tap.start(); diff --git a/test/test.corpus-master.ts b/test/test.corpus-master.ts new file mode 100644 index 0000000..2b5275c --- /dev/null +++ b/test/test.corpus-master.ts @@ -0,0 +1,213 @@ +import { tap, expect } from '@push.rocks/tapbundle'; +import * as fs from 'fs/promises'; +import * as path from 'path'; +import { execSync } from 'child_process'; + +// Master test for corpus testing +tap.test('Run all corpus tests', async () => { + console.log('Running all corpus tests...'); + + // Create output directory + const testDir = path.join(process.cwd(), 'test', 'output'); + await fs.mkdir(testDir, { recursive: true }); + + // Run each test file and collect results + const testFiles = [ + 'test.zugferd-corpus.ts', + 'test.xml-rechnung-corpus.ts', + 'test.other-formats-corpus.ts', + 'test.validation-corpus.ts', + 'test.circular-corpus.ts' + ]; + + const results: Record = {}; + + for (const testFile of testFiles) { + console.log(`Running ${testFile}...`); + + try { + // Run the test + execSync(`tsx test/${testFile}`, { stdio: 'inherit' }); + + // Read the results + const resultFile = testFile.replace('.ts', '-results.json'); + const resultPath = path.join(testDir, resultFile); + + if (await fileExists(resultPath)) { + const resultContent = await fs.readFile(resultPath, 'utf8'); + results[testFile] = JSON.parse(resultContent); + } else { + results[testFile] = { error: 'No results file found' }; + } + } catch (error) { + console.error(`Error running ${testFile}:`, error); + results[testFile] = { error: error.message }; + } + } + + // Save the combined results + await fs.writeFile( + path.join(testDir, 'corpus-master-results.json'), + JSON.stringify(results, null, 2) + ); + + // Generate a summary report + const summary = generateSummary(results); + await fs.writeFile( + path.join(testDir, 'corpus-summary.md'), + summary + ); + + console.log('All corpus tests completed.'); +}); + +/** + * Generates a summary report from the test results + * @param results Test results + * @returns Summary report in Markdown format + */ +function generateSummary(results: Record): string { + let summary = '# XInvoice Corpus Testing Summary\n\n'; + + // Add date and time + summary += `Generated on: ${new Date().toISOString()}\n\n`; + + // Add overall summary + summary += '## Overall Summary\n\n'; + summary += '| Test | Success Rate | Files Tested |\n'; + summary += '|------|--------------|-------------|\n'; + + for (const [testFile, result] of Object.entries(results)) { + if (result.error) { + summary += `| ${testFile} | Error: ${result.error} | N/A |\n`; + continue; + } + + let successRate = 'N/A'; + let filesTested = 'N/A'; + + if (testFile === 'test.zugferd-corpus.ts') { + const rate = result.totalCorrectSuccessRate * 100; + successRate = `${rate.toFixed(2)}%`; + + const v1Correct = result.zugferdV1Correct?.success + result.zugferdV1Correct?.fail || 0; + const v1Fail = result.zugferdV1Fail?.success + result.zugferdV1Fail?.fail || 0; + const v2Correct = result.zugferdV2Correct?.success + result.zugferdV2Correct?.fail || 0; + const v2Fail = result.zugferdV2Fail?.success + result.zugferdV2Fail?.fail || 0; + + filesTested = `${v1Correct + v1Fail + v2Correct + v2Fail}`; + } else if (testFile === 'test.xml-rechnung-corpus.ts') { + const rate = result.totalSuccessRate * 100; + successRate = `${rate.toFixed(2)}%`; + + const cii = result.cii?.success + result.cii?.fail || 0; + const ubl = result.ubl?.success + result.ubl?.fail || 0; + const fx = result.fx?.success + result.fx?.fail || 0; + + filesTested = `${cii + ubl + fx}`; + } else if (testFile === 'test.other-formats-corpus.ts') { + const rate = result.totalSuccessRate * 100; + successRate = `${rate.toFixed(2)}%`; + + const peppol = result.peppol?.success + result.peppol?.fail || 0; + const fatturapa = result.fatturapa?.success + result.fatturapa?.fail || 0; + + filesTested = `${peppol + fatturapa}`; + } else if (testFile === 'test.validation-corpus.ts') { + const rate = result.totalCorrectSuccessRate * 100; + successRate = `${rate.toFixed(2)}%`; + + const zugferdV2Correct = result.zugferdV2Correct?.success + result.zugferdV2Correct?.fail || 0; + const zugferdV2Fail = result.zugferdV2Fail?.success + result.zugferdV2Fail?.fail || 0; + const cii = result.cii?.success + result.cii?.fail || 0; + const ubl = result.ubl?.success + result.ubl?.fail || 0; + + filesTested = `${zugferdV2Correct + zugferdV2Fail + cii + ubl}`; + } else if (testFile === 'test.circular-corpus.ts') { + const rate = result.totalSuccessRate * 100; + successRate = `${rate.toFixed(2)}%`; + + const cii = result.cii?.success + result.cii?.fail || 0; + const ubl = result.ubl?.success + result.ubl?.fail || 0; + + filesTested = `${cii + ubl}`; + } + + summary += `| ${testFile} | ${successRate} | ${filesTested} |\n`; + } + + // Add detailed results for each test + for (const [testFile, result] of Object.entries(results)) { + if (result.error) { + continue; + } + + summary += `\n## ${testFile}\n\n`; + + if (testFile === 'test.zugferd-corpus.ts') { + summary += '### ZUGFeRD v1 Correct Files\n\n'; + summary += `Success: ${result.zugferdV1Correct?.success || 0}, Fail: ${result.zugferdV1Correct?.fail || 0}\n\n`; + + summary += '### ZUGFeRD v1 Fail Files\n\n'; + summary += `Success: ${result.zugferdV1Fail?.success || 0}, Fail: ${result.zugferdV1Fail?.fail || 0}\n\n`; + + summary += '### ZUGFeRD v2 Correct Files\n\n'; + summary += `Success: ${result.zugferdV2Correct?.success || 0}, Fail: ${result.zugferdV2Correct?.fail || 0}\n\n`; + + summary += '### ZUGFeRD v2 Fail Files\n\n'; + summary += `Success: ${result.zugferdV2Fail?.success || 0}, Fail: ${result.zugferdV2Fail?.fail || 0}\n\n`; + } else if (testFile === 'test.xml-rechnung-corpus.ts') { + summary += '### CII Files\n\n'; + summary += `Success: ${result.cii?.success || 0}, Fail: ${result.cii?.fail || 0}\n\n`; + + summary += '### UBL Files\n\n'; + summary += `Success: ${result.ubl?.success || 0}, Fail: ${result.ubl?.fail || 0}\n\n`; + + summary += '### FX Files\n\n'; + summary += `Success: ${result.fx?.success || 0}, Fail: ${result.fx?.fail || 0}\n\n`; + } else if (testFile === 'test.other-formats-corpus.ts') { + summary += '### PEPPOL Files\n\n'; + summary += `Success: ${result.peppol?.success || 0}, Fail: ${result.peppol?.fail || 0}\n\n`; + + summary += '### fatturaPA Files\n\n'; + summary += `Success: ${result.fatturapa?.success || 0}, Fail: ${result.fatturapa?.fail || 0}\n\n`; + } else if (testFile === 'test.validation-corpus.ts') { + summary += '### ZUGFeRD v2 Correct Files Validation\n\n'; + summary += `Success: ${result.zugferdV2Correct?.success || 0}, Fail: ${result.zugferdV2Correct?.fail || 0}\n\n`; + + summary += '### ZUGFeRD v2 Fail Files Validation\n\n'; + summary += `Success: ${result.zugferdV2Fail?.success || 0}, Fail: ${result.zugferdV2Fail?.fail || 0}\n\n`; + + summary += '### CII Files Validation\n\n'; + summary += `Success: ${result.cii?.success || 0}, Fail: ${result.cii?.fail || 0}\n\n`; + + summary += '### UBL Files Validation\n\n'; + summary += `Success: ${result.ubl?.success || 0}, Fail: ${result.ubl?.fail || 0}\n\n`; + } else if (testFile === 'test.circular-corpus.ts') { + summary += '### CII Files Circular Testing\n\n'; + summary += `Success: ${result.cii?.success || 0}, Fail: ${result.cii?.fail || 0}\n\n`; + + summary += '### UBL Files Circular Testing\n\n'; + summary += `Success: ${result.ubl?.success || 0}, Fail: ${result.ubl?.fail || 0}\n\n`; + } + } + + return summary; +} + +/** + * Checks if a file exists + * @param filePath Path to the file + * @returns True if the file exists + */ +async function fileExists(filePath: string): Promise { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } +} + +// Run the tests +tap.start(); diff --git a/test/test.focused-corpus.ts b/test/test.focused-corpus.ts new file mode 100644 index 0000000..fc6658a --- /dev/null +++ b/test/test.focused-corpus.ts @@ -0,0 +1,279 @@ +import { tap, expect } from '@push.rocks/tapbundle'; +import { XInvoice } from '../ts/classes.xinvoice.js'; +import { InvoiceFormat } from '../ts/interfaces/common.js'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +// Test a focused subset of corpus files +tap.test('XInvoice should handle a focused subset of corpus files', async () => { + // Get a small subset of files for focused testing + const ciiFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII'), '.xml', 5); + const ublFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL'), '.xml', 5); + const zugferdV2Files = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931'), '.pdf', 5); + + // Log the number of files found + console.log(`Found ${ciiFiles.length} CII files for focused testing`); + console.log(`Found ${ublFiles.length} UBL files for focused testing`); + console.log(`Found ${zugferdV2Files.length} ZUGFeRD v2 files for focused testing`); + + // Test CII files + console.log('\nTesting CII files:'); + for (const file of ciiFiles) { + console.log(`\nTesting file: ${path.basename(file)}`); + await testXmlFile(file, InvoiceFormat.CII); + } + + // Test UBL files + console.log('\nTesting UBL files:'); + for (const file of ublFiles) { + console.log(`\nTesting file: ${path.basename(file)}`); + await testXmlFile(file, InvoiceFormat.UBL); + } + + // Test ZUGFeRD v2 files + console.log('\nTesting ZUGFeRD v2 files:'); + for (const file of zugferdV2Files) { + console.log(`\nTesting file: ${path.basename(file)}`); + await testPdfFile(file); + } + + // Create a test directory for output + const testDir = path.join(process.cwd(), 'test', 'output', 'focused'); + await fs.mkdir(testDir, { recursive: true }); + + // Success - we're just testing individual files + expect(true).toBeTrue(); +}); + +/** + * Tests an XML file + * @param file File to test + * @param expectedFormat Expected format + */ +async function testXmlFile(file: string, expectedFormat: InvoiceFormat): Promise { + try { + // Read the file + const xmlContent = await fs.readFile(file, 'utf8'); + + // Create XInvoice from XML + const xinvoice = await XInvoice.fromXml(xmlContent); + + // Check that the XInvoice instance has the expected properties + if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) { + // Check that the format is detected correctly + const format = xinvoice.getFormat(); + const isCorrectFormat = format === expectedFormat || + (expectedFormat === InvoiceFormat.CII && format === InvoiceFormat.FACTURX) || + (expectedFormat === InvoiceFormat.FACTURX && format === InvoiceFormat.CII) || + (expectedFormat === InvoiceFormat.UBL && format === InvoiceFormat.XRECHNUNG) || + (expectedFormat === InvoiceFormat.XRECHNUNG && format === InvoiceFormat.UBL); + + if (isCorrectFormat) { + // Try to export the invoice back to XML + try { + let exportFormat = 'facturx'; + if (format === InvoiceFormat.UBL || format === InvoiceFormat.XRECHNUNG) { + exportFormat = 'xrechnung'; + } + + const exportedXml = await xinvoice.exportXml(exportFormat as any); + + if (exportedXml) { + console.log('✅ Success: File loaded, format detected correctly, and exported successfully'); + console.log(`Format: ${format}`); + console.log(`From: ${xinvoice.from.name}`); + console.log(`To: ${xinvoice.to.name}`); + console.log(`Items: ${xinvoice.items.length}`); + + // Save the exported XML for inspection + const testDir = path.join(process.cwd(), 'test', 'output', 'focused'); + await fs.mkdir(testDir, { recursive: true }); + await fs.writeFile(path.join(testDir, `${path.basename(file)}-exported.xml`), exportedXml); + } else { + console.log('❌ Failed to export valid XML'); + } + } catch (exportError) { + console.log(`❌ Export error: ${exportError.message}`); + } + } else { + console.log(`❌ Wrong format detected: ${format}, expected: ${expectedFormat}`); + } + } else { + console.log('❌ Missing required properties'); + } + } catch (error) { + console.log(`❌ Error processing the file: ${error.message}`); + } +} + +/** + * Tests a PDF file + * @param file File to test + */ +async function testPdfFile(file: string): Promise { + try { + // Read the file + const pdfBuffer = await fs.readFile(file); + + // Extract XML from PDF + const { PDFExtractor } = await import('../ts/formats/pdf/pdf.extractor.js'); + const extractor = new PDFExtractor(); + const xmlContent = await extractor.extractXml(pdfBuffer); + + // Save the raw XML content for inspection, even if it's invalid + const testDir = path.join(process.cwd(), 'test', 'output', 'focused'); + await fs.mkdir(testDir, { recursive: true }); + + // Try to get the raw XML content directly from the PDF + try { + const pdfDoc = await import('pdf-lib').then(lib => lib.PDFDocument.load(pdfBuffer)); + const namesDictObj = pdfDoc.catalog.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('Names'))); + + if (namesDictObj) { + const embeddedFilesDictObj = namesDictObj.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('EmbeddedFiles'))); + + if (embeddedFilesDictObj) { + const filesSpecObj = embeddedFilesDictObj.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('Names'))); + + if (filesSpecObj && filesSpecObj.size && filesSpecObj.size() > 0) { + for (let i = 0; i < filesSpecObj.size(); i += 2) { + const fileNameObj = filesSpecObj.lookup(i); + const fileSpecObj = filesSpecObj.lookup(i + 1); + + if (fileNameObj && fileSpecObj) { + const fileName = fileNameObj.toString(); + console.log(`Found embedded file: ${fileName}`); + + const efDictObj = fileSpecObj.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('EF'))); + + if (efDictObj) { + const maybeStream = efDictObj.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('F'))); + + if (maybeStream) { + try { + const xmlBytes = maybeStream.getContents(); + const rawXmlContent = new TextDecoder('utf-8').decode(xmlBytes); + + await fs.writeFile(path.join(testDir, `${path.basename(file)}-raw-${fileName}.xml`), rawXmlContent); + console.log(`Saved raw XML content from ${fileName}`); + } catch (streamError) { + console.log(`Error extracting stream content: ${streamError.message}`); + } + } + } + } + } + } + } + } + } catch (pdfError) { + console.log(`Error inspecting PDF structure: ${pdfError.message}`); + } + + if (xmlContent) { + console.log('✅ Successfully extracted XML from PDF'); + + // Save the extracted XML for inspection + await fs.writeFile(path.join(testDir, `${path.basename(file)}-extracted.xml`), xmlContent); + + // Try to create XInvoice from the extracted XML + try { + const xinvoice = await XInvoice.fromXml(xmlContent); + + if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) { + console.log('✅ Successfully created XInvoice from extracted XML'); + console.log(`Format: ${xinvoice.getFormat()}`); + console.log(`From: ${xinvoice.from.name}`); + console.log(`To: ${xinvoice.to.name}`); + console.log(`Items: ${xinvoice.items.length}`); + + // Try to export the invoice back to XML + try { + const exportedXml = await xinvoice.exportXml('facturx'); + + if (exportedXml) { + console.log('✅ Successfully exported XInvoice back to XML'); + + // Save the exported XML for inspection + await fs.writeFile(path.join(testDir, `${path.basename(file)}-reexported.xml`), exportedXml); + } else { + console.log('❌ Failed to export valid XML'); + } + } catch (exportError) { + console.log(`❌ Export error: ${exportError.message}`); + } + } else { + console.log('❌ Missing required properties in created XInvoice'); + } + } catch (xmlError) { + console.log(`❌ Error creating XInvoice from extracted XML: ${xmlError.message}`); + } + } else { + console.log('❌ No XML found in PDF'); + } + + // Try to create XInvoice directly from PDF + try { + const xinvoice = await XInvoice.fromPdf(pdfBuffer); + + if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) { + console.log('✅ Successfully created XInvoice directly from PDF'); + console.log(`Format: ${xinvoice.getFormat()}`); + console.log(`From: ${xinvoice.from.name}`); + console.log(`To: ${xinvoice.to.name}`); + console.log(`Items: ${xinvoice.items.length}`); + } else { + console.log('❌ Missing required properties in created XInvoice'); + } + } catch (pdfError) { + console.log(`❌ Error creating XInvoice directly from PDF: ${pdfError.message}`); + } + } catch (error) { + console.log(`❌ Error processing the file: ${error.message}`); + } +} + +/** + * Recursively finds files with a specific extension in a directory + * @param dir Directory to search + * @param extension File extension to look for + * @param limit Maximum number of files to return + * @returns Array of file paths + */ +async function findFiles(dir: string, extension: string, limit?: number): Promise { + try { + const files = await fs.readdir(dir, { withFileTypes: true }); + + const result: string[] = []; + + for (const file of files) { + if (limit && result.length >= limit) { + break; + } + + const filePath = path.join(dir, file.name); + + if (file.isDirectory()) { + // Recursively search subdirectories + const remainingLimit = limit ? limit - result.length : undefined; + const subDirFiles = await findFiles(filePath, extension, remainingLimit); + result.push(...subDirFiles); + + if (limit && result.length >= limit) { + break; + } + } else if (file.name.toLowerCase().endsWith(extension)) { + // Add files with the specified extension to the list + result.push(filePath); + } + } + + return result; + } catch (error) { + console.error(`Error finding files in ${dir}:`, error); + return []; + } +} + +// Run the tests +tap.start(); diff --git a/test/test.other-formats-corpus.ts b/test/test.other-formats-corpus.ts new file mode 100644 index 0000000..0307afc --- /dev/null +++ b/test/test.other-formats-corpus.ts @@ -0,0 +1,172 @@ +import { tap, expect } from '@push.rocks/tapbundle'; +import { XInvoice } from '../ts/classes.xinvoice.js'; +import { InvoiceFormat, ValidationLevel } from '../ts/interfaces/common.js'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +// Test other formats corpus (PEPPOL, fatturaPA) +tap.test('XInvoice should handle other formats corpus', async () => { + // Get all files + const peppolFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/PEPPOL'), '.xml'); + + // Skip problematic fatturaPA files + const fatturapaDir = path.join(process.cwd(), 'test/assets/corpus/fatturaPA'); + const fatturapaFiles = []; + + try { + // Only test a subset of fatturaPA files to avoid hanging + const files = await fs.readdir(fatturapaDir, { withFileTypes: true }); + for (const file of files) { + if (!file.isDirectory() && file.name.endsWith('.xml') && !file.name.includes('Large_Invoice')) { + fatturapaFiles.push(path.join(fatturapaDir, file.name)); + } + } + } catch (error) { + console.error(`Error reading fatturaPA directory: ${error.message}`); + } + + // Log the number of files found + console.log(`Found ${peppolFiles.length} PEPPOL files`); + console.log(`Found ${fatturapaFiles.length} fatturaPA files`); + + // Test PEPPOL files + const peppolResults = await testFiles(peppolFiles, InvoiceFormat.UBL); + console.log(`PEPPOL files: ${peppolResults.success} succeeded, ${peppolResults.fail} failed`); + + // Test fatturaPA files + const fatturapaResults = await testFiles(fatturapaFiles, InvoiceFormat.UBL); + console.log(`fatturaPA files: ${fatturapaResults.success} succeeded, ${fatturapaResults.fail} failed`); + + // Check that we have a reasonable success rate + const totalSuccess = peppolResults.success + fatturapaResults.success; + const totalFiles = peppolFiles.length + fatturapaFiles.length; + const successRate = totalSuccess / totalFiles; + + console.log(`Overall success rate: ${(successRate * 100).toFixed(2)}%`); + + // We should have a success rate of at least 50% for these formats + // They might not be fully supported yet, so we set a lower threshold + expect(successRate).toBeGreaterThan(0.5); + + // Save the test results to a file + const testDir = path.join(process.cwd(), 'test', 'output'); + await fs.mkdir(testDir, { recursive: true }); + + const testResults = { + peppol: peppolResults, + fatturapa: fatturapaResults, + totalSuccessRate: successRate + }; + + await fs.writeFile( + path.join(testDir, 'other-formats-corpus-results.json'), + JSON.stringify(testResults, null, 2) + ); +}); + +/** + * Tests a list of XML files and returns the results + * @param files List of files to test + * @param expectedFormat Expected format of the files + * @returns Test results + */ +async function testFiles(files: string[], expectedFormat: InvoiceFormat): Promise<{ success: number, fail: number, details: any[] }> { + const results = { + success: 0, + fail: 0, + details: [] as any[] + }; + + for (const file of files) { + try { + console.log(`Testing file: ${path.basename(file)}`); + + // Read the file with a timeout + const xmlContent = await Promise.race([ + fs.readFile(file, 'utf8'), + new Promise((_, reject) => { + setTimeout(() => reject(new Error('Timeout reading file')), 5000); + }) + ]); + + // Create XInvoice from XML with a timeout + const xinvoice = await Promise.race([ + XInvoice.fromXml(xmlContent), + new Promise((_, reject) => { + setTimeout(() => reject(new Error('Timeout processing XML')), 5000); + }) + ]); + + // Check that the XInvoice instance has the expected properties + if (xinvoice && xinvoice.from && xinvoice.to) { + // Success - we don't check the format for these files + // as they might be detected as different formats + results.success++; + results.details.push({ + file, + success: true, + format: xinvoice.getFormat(), + error: null + }); + console.log(`✅ Success: ${path.basename(file)}`); + } else { + // Missing required properties + results.fail++; + results.details.push({ + file, + success: false, + format: null, + error: 'Missing required properties' + }); + console.log(`❌ Failed: ${path.basename(file)} - Missing required properties`); + } + } catch (error) { + // Error processing the file + results.fail++; + results.details.push({ + file, + success: false, + format: null, + error: `Error: ${error.message}` + }); + console.log(`❌ Failed: ${path.basename(file)} - ${error.message}`); + } + } + + return results; +} + +/** + * Recursively finds files with a specific extension in a directory + * @param dir Directory to search + * @param extension File extension to look for + * @returns Array of file paths + */ +async function findFiles(dir: string, extension: string): Promise { + try { + const files = await fs.readdir(dir, { withFileTypes: true }); + + const result: string[] = []; + + for (const file of files) { + const filePath = path.join(dir, file.name); + + if (file.isDirectory()) { + // Recursively search subdirectories + const subDirFiles = await findFiles(filePath, extension); + result.push(...subDirFiles); + } else if (file.name.toLowerCase().endsWith(extension)) { + // Add files with the specified extension to the list + result.push(filePath); + } + } + + return result; + } catch (error) { + console.error(`Error finding files in ${dir}:`, error); + return []; + } +} + +// Run the tests +tap.start(); diff --git a/test/test.simple-corpus.ts b/test/test.simple-corpus.ts new file mode 100644 index 0000000..b0bbd57 --- /dev/null +++ b/test/test.simple-corpus.ts @@ -0,0 +1,81 @@ +import { tap, expect } from '@push.rocks/tapbundle'; +import { XInvoice } from '../ts/classes.xinvoice.js'; +import { InvoiceFormat } from '../ts/interfaces/common.js'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +// Test a simple subset of corpus files +tap.test('XInvoice should handle a simple subset of corpus files', async () => { + // Test a few specific files that we know work + const testFiles = [ + // CII files + path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII/EN16931_Einfach.cii.xml'), + // UBL files + path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL/EN16931_Einfach.ubl.xml'), + // PEPPOL files (if available) + path.join(process.cwd(), 'test/assets/corpus/PEPPOL/peppol-bis-invoice-3-sample.xml') + ]; + + // Test each file + for (const file of testFiles) { + try { + console.log(`\nTesting file: ${path.basename(file)}`); + + // Check if file exists + try { + await fs.access(file); + } catch (error) { + console.log(`File not found: ${file}`); + continue; + } + + // Read the file + const xmlContent = await fs.readFile(file, 'utf8'); + + // Create XInvoice from XML + const xinvoice = await XInvoice.fromXml(xmlContent); + + // Check that the XInvoice instance has the expected properties + if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) { + console.log('✅ Success: File loaded and parsed successfully'); + console.log(`Format: ${xinvoice.getFormat()}`); + console.log(`From: ${xinvoice.from.name}`); + console.log(`To: ${xinvoice.to.name}`); + console.log(`Items: ${xinvoice.items.length}`); + + // Try to export the invoice back to XML + try { + let exportFormat = 'facturx'; + if (xinvoice.getFormat() === InvoiceFormat.UBL || xinvoice.getFormat() === InvoiceFormat.XRECHNUNG) { + exportFormat = 'xrechnung'; + } + + const exportedXml = await xinvoice.exportXml(exportFormat as any); + + if (exportedXml) { + console.log('✅ Successfully exported back to XML'); + + // Save the exported XML for inspection + const testDir = path.join(process.cwd(), 'test', 'output', 'simple'); + await fs.mkdir(testDir, { recursive: true }); + await fs.writeFile(path.join(testDir, `${path.basename(file)}-exported.xml`), exportedXml); + } else { + console.log('❌ Failed to export valid XML'); + } + } catch (exportError) { + console.log(`❌ Export error: ${exportError.message}`); + } + } else { + console.log('❌ Missing required properties'); + } + } catch (error) { + console.log(`❌ Error processing the file: ${error.message}`); + } + } + + // Success - we're just testing individual files + expect(true).toBeTrue(); +}); + +// Run the tests +tap.start(); diff --git a/test/test.validation-corpus.ts b/test/test.validation-corpus.ts new file mode 100644 index 0000000..5c3a652 --- /dev/null +++ b/test/test.validation-corpus.ts @@ -0,0 +1,177 @@ +import { tap, expect } from '@push.rocks/tapbundle'; +import { XInvoice } from '../ts/classes.xinvoice.js'; +import { InvoiceFormat, ValidationLevel } from '../ts/interfaces/common.js'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +// Test validation of corpus files +tap.test('XInvoice should validate corpus files correctly', async () => { + // Get a subset of files for validation testing + const zugferdV2CorrectFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/correct'), '.pdf', 5); + const zugferdV2FailFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/fail'), '.pdf', 5); + const ciiFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII'), '.xml', 5); + const ublFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL'), '.xml', 5); + + // Log the number of files found + console.log(`Found ${zugferdV2CorrectFiles.length} ZUGFeRD v2 correct files for validation`); + console.log(`Found ${zugferdV2FailFiles.length} ZUGFeRD v2 fail files for validation`); + console.log(`Found ${ciiFiles.length} CII files for validation`); + console.log(`Found ${ublFiles.length} UBL files for validation`); + + // Test ZUGFeRD v2 correct files + const zugferdV2CorrectResults = await testValidation(zugferdV2CorrectFiles, true, true); + console.log(`ZUGFeRD v2 correct files validation: ${zugferdV2CorrectResults.success} succeeded, ${zugferdV2CorrectResults.fail} failed`); + + // Test ZUGFeRD v2 fail files + const zugferdV2FailResults = await testValidation(zugferdV2FailFiles, true, false); + console.log(`ZUGFeRD v2 fail files validation: ${zugferdV2FailResults.success} succeeded, ${zugferdV2FailResults.fail} failed`); + + // Test CII files + const ciiResults = await testValidation(ciiFiles, false, true); + console.log(`CII files validation: ${ciiResults.success} succeeded, ${ciiResults.fail} failed`); + + // Test UBL files + const ublResults = await testValidation(ublFiles, false, true); + console.log(`UBL files validation: ${ublResults.success} succeeded, ${ublResults.fail} failed`); + + // Check that we have a reasonable success rate for correct files + const totalCorrectSuccess = zugferdV2CorrectResults.success + ciiResults.success + ublResults.success; + const totalCorrectFiles = zugferdV2CorrectFiles.length + ciiFiles.length + ublFiles.length; + const correctSuccessRate = totalCorrectSuccess / totalCorrectFiles; + + console.log(`Overall success rate for correct files validation: ${(correctSuccessRate * 100).toFixed(2)}%`); + + // We should have a success rate of at least 60% for correct files + // Note: This is lower than ideal because we haven't implemented the XRechnung validator yet + expect(correctSuccessRate).toBeGreaterThan(0.6); + + // Save the test results to a file + const testDir = path.join(process.cwd(), 'test', 'output'); + await fs.mkdir(testDir, { recursive: true }); + + const testResults = { + zugferdV2Correct: zugferdV2CorrectResults, + zugferdV2Fail: zugferdV2FailResults, + cii: ciiResults, + ubl: ublResults, + totalCorrectSuccessRate: correctSuccessRate + }; + + await fs.writeFile( + path.join(testDir, 'validation-corpus-results.json'), + JSON.stringify(testResults, null, 2) + ); +}); + +/** + * Tests validation of files and returns the results + * @param files List of files to test + * @param isPdf Whether the files are PDFs + * @param expectValid Whether we expect the files to be valid + * @returns Test results + */ +async function testValidation(files: string[], isPdf: boolean, expectValid: boolean): Promise<{ success: number, fail: number, details: any[] }> { + const results = { + success: 0, + fail: 0, + details: [] as any[] + }; + + for (const file of files) { + try { + // Create XInvoice from file + let xinvoice: XInvoice; + + if (isPdf) { + const fileBuffer = await fs.readFile(file); + xinvoice = await XInvoice.fromPdf(fileBuffer); + } else { + const xmlContent = await fs.readFile(file, 'utf8'); + xinvoice = await XInvoice.fromXml(xmlContent); + } + + // Validate the invoice + const validationResult = await xinvoice.validate(ValidationLevel.SYNTAX); + + // Check if the validation result matches our expectation + if (validationResult.valid === expectValid) { + // Success + results.success++; + results.details.push({ + file, + success: true, + valid: validationResult.valid, + errors: validationResult.errors, + error: null + }); + } else { + // Validation result doesn't match expectation + results.fail++; + results.details.push({ + file, + success: false, + valid: validationResult.valid, + errors: validationResult.errors, + error: `Validation result (${validationResult.valid}) doesn't match expectation (${expectValid})` + }); + } + } catch (error) { + // Error processing the file + results.fail++; + results.details.push({ + file, + success: false, + valid: null, + errors: null, + error: `Error: ${error.message}` + }); + } + } + + return results; +} + +/** + * Recursively finds files with a specific extension in a directory + * @param dir Directory to search + * @param extension File extension to look for + * @param limit Maximum number of files to return + * @returns Array of file paths + */ +async function findFiles(dir: string, extension: string, limit?: number): Promise { + try { + const files = await fs.readdir(dir, { withFileTypes: true }); + + const result: string[] = []; + + for (const file of files) { + if (limit && result.length >= limit) { + break; + } + + const filePath = path.join(dir, file.name); + + if (file.isDirectory()) { + // Recursively search subdirectories + const remainingLimit = limit ? limit - result.length : undefined; + const subDirFiles = await findFiles(filePath, extension, remainingLimit); + result.push(...subDirFiles); + + if (limit && result.length >= limit) { + break; + } + } else if (file.name.toLowerCase().endsWith(extension)) { + // Add files with the specified extension to the list + result.push(filePath); + } + } + + return result; + } catch (error) { + console.error(`Error finding files in ${dir}:`, error); + return []; + } +} + +// Run the tests +tap.start(); diff --git a/test/test.xml-rechnung-corpus.ts b/test/test.xml-rechnung-corpus.ts new file mode 100644 index 0000000..5db7d33 --- /dev/null +++ b/test/test.xml-rechnung-corpus.ts @@ -0,0 +1,196 @@ +import { tap, expect } from '@push.rocks/tapbundle'; +import { XInvoice } from '../ts/classes.xinvoice.js'; +import { InvoiceFormat, ValidationLevel } from '../ts/interfaces/common.js'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +// Test XML-Rechnung corpus (CII and UBL) +tap.test('XInvoice should handle XML-Rechnung corpus', async () => { + // Get all XML-Rechnung files + const ciiFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII'), '.xml'); + const ublFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL'), '.xml'); + const fxFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/FX'), '.xml'); + + // Log the number of files found + console.log(`Found ${ciiFiles.length} CII files`); + console.log(`Found ${ublFiles.length} UBL files`); + console.log(`Found ${fxFiles.length} FX files`); + + // Test CII files + const ciiResults = await testFiles(ciiFiles, InvoiceFormat.CII); + console.log(`CII files: ${ciiResults.success} succeeded, ${ciiResults.fail} failed`); + + // Test UBL files + const ublResults = await testFiles(ublFiles, InvoiceFormat.UBL); + console.log(`UBL files: ${ublResults.success} succeeded, ${ublResults.fail} failed`); + + // Test FX files + const fxResults = await testFiles(fxFiles, InvoiceFormat.FACTURX); + console.log(`FX files: ${fxResults.success} succeeded, ${fxResults.fail} failed`); + + // Check that we have a reasonable success rate + const totalSuccess = ciiResults.success + ublResults.success + fxResults.success; + const totalFiles = ciiFiles.length + ublFiles.length + fxFiles.length; + const successRate = totalSuccess / totalFiles; + + console.log(`Overall success rate: ${(successRate * 100).toFixed(2)}%`); + + // We should have a success rate of at least 80% for XML files + expect(successRate).toBeGreaterThan(0.8); + + // Save the test results to a file + const testDir = path.join(process.cwd(), 'test', 'output'); + await fs.mkdir(testDir, { recursive: true }); + + const testResults = { + cii: ciiResults, + ubl: ublResults, + fx: fxResults, + totalSuccessRate: successRate + }; + + await fs.writeFile( + path.join(testDir, 'xml-rechnung-corpus-results.json'), + JSON.stringify(testResults, null, 2) + ); +}); + +/** + * Tests a list of XML files and returns the results + * @param files List of files to test + * @param expectedFormat Expected format of the files + * @returns Test results + */ +async function testFiles(files: string[], expectedFormat: InvoiceFormat): Promise<{ success: number, fail: number, details: any[] }> { + const results = { + success: 0, + fail: 0, + details: [] as any[] + }; + + for (const file of files) { + try { + // Read the file + const xmlContent = await fs.readFile(file, 'utf8'); + + // Create XInvoice from XML + const xinvoice = await XInvoice.fromXml(xmlContent); + + // Check that the XInvoice instance has the expected properties + if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) { + // Check that the format is detected correctly + const format = xinvoice.getFormat(); + const isCorrectFormat = format === expectedFormat || + (expectedFormat === InvoiceFormat.CII && format === InvoiceFormat.FACTURX) || + (expectedFormat === InvoiceFormat.FACTURX && format === InvoiceFormat.CII) || + (expectedFormat === InvoiceFormat.UBL && format === InvoiceFormat.XRECHNUNG) || + (expectedFormat === InvoiceFormat.XRECHNUNG && format === InvoiceFormat.UBL); + + if (isCorrectFormat) { + // Try to export the invoice back to XML + try { + let exportFormat = 'facturx'; + if (format === InvoiceFormat.UBL || format === InvoiceFormat.XRECHNUNG) { + exportFormat = 'xrechnung'; + } + + const exportedXml = await xinvoice.exportXml(exportFormat as any); + + if (exportedXml) { + // Success + results.success++; + results.details.push({ + file, + success: true, + format, + error: null + }); + } else { + // Failed to export valid XML + results.fail++; + results.details.push({ + file, + success: false, + format, + error: 'Failed to export valid XML' + }); + } + } catch (exportError) { + // Failed to export XML + results.fail++; + results.details.push({ + file, + success: false, + format, + error: `Export error: ${exportError.message}` + }); + } + } else { + // Wrong format detected + results.fail++; + results.details.push({ + file, + success: false, + format, + error: `Wrong format detected: ${format}, expected: ${expectedFormat}` + }); + } + } else { + // Missing required properties + results.fail++; + results.details.push({ + file, + success: false, + format: null, + error: 'Missing required properties' + }); + } + } catch (error) { + // Error processing the file + results.fail++; + results.details.push({ + file, + success: false, + format: null, + error: `Error: ${error.message}` + }); + } + } + + return results; +} + +/** + * Recursively finds files with a specific extension in a directory + * @param dir Directory to search + * @param extension File extension to look for + * @returns Array of file paths + */ +async function findFiles(dir: string, extension: string): Promise { + try { + const files = await fs.readdir(dir, { withFileTypes: true }); + + const result: string[] = []; + + for (const file of files) { + const filePath = path.join(dir, file.name); + + if (file.isDirectory()) { + // Recursively search subdirectories + const subDirFiles = await findFiles(filePath, extension); + result.push(...subDirFiles); + } else if (file.name.toLowerCase().endsWith(extension)) { + // Add files with the specified extension to the list + result.push(filePath); + } + } + + return result; + } catch (error) { + console.error(`Error finding files in ${dir}:`, error); + return []; + } +} + +// Run the tests +tap.start(); diff --git a/test/test.zugferd-corpus.ts b/test/test.zugferd-corpus.ts new file mode 100644 index 0000000..52183a0 --- /dev/null +++ b/test/test.zugferd-corpus.ts @@ -0,0 +1,205 @@ +import { tap, expect } from '@push.rocks/tapbundle'; +import { XInvoice } from '../ts/classes.xinvoice.js'; +import { InvoiceFormat, ValidationLevel } from '../ts/interfaces/common.js'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +// Test ZUGFeRD v1 and v2 corpus +tap.test('XInvoice should handle ZUGFeRD v1 and v2 corpus', async () => { + // Get all ZUGFeRD files + const zugferdV1CorrectFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv1/correct'), '.pdf'); + const zugferdV1FailFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv1/fail'), '.pdf'); + const zugferdV2CorrectFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/correct'), '.pdf'); + const zugferdV2FailFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/fail'), '.pdf'); + + // Log the number of files found + console.log(`Found ${zugferdV1CorrectFiles.length} ZUGFeRD v1 correct files`); + console.log(`Found ${zugferdV1FailFiles.length} ZUGFeRD v1 fail files`); + console.log(`Found ${zugferdV2CorrectFiles.length} ZUGFeRD v2 correct files`); + console.log(`Found ${zugferdV2FailFiles.length} ZUGFeRD v2 fail files`); + + // Test ZUGFeRD v1 correct files + const v1CorrectResults = await testFiles(zugferdV1CorrectFiles, true); + console.log(`ZUGFeRD v1 correct files: ${v1CorrectResults.success} succeeded, ${v1CorrectResults.fail} failed`); + + // Test ZUGFeRD v1 fail files + const v1FailResults = await testFiles(zugferdV1FailFiles, false); + console.log(`ZUGFeRD v1 fail files: ${v1FailResults.success} succeeded, ${v1FailResults.fail} failed`); + + // Test ZUGFeRD v2 correct files + const v2CorrectResults = await testFiles(zugferdV2CorrectFiles, true); + console.log(`ZUGFeRD v2 correct files: ${v2CorrectResults.success} succeeded, ${v2CorrectResults.fail} failed`); + + // Test ZUGFeRD v2 fail files + const v2FailResults = await testFiles(zugferdV2FailFiles, false); + console.log(`ZUGFeRD v2 fail files: ${v2FailResults.fail} succeeded, ${v2FailResults.success} failed`); + + // Check that we have a reasonable success rate for correct files + const totalCorrect = v1CorrectResults.success + v2CorrectResults.success; + const totalCorrectFiles = zugferdV1CorrectFiles.length + zugferdV2CorrectFiles.length; + const correctSuccessRate = totalCorrect / totalCorrectFiles; + + console.log(`Overall success rate for correct files: ${(correctSuccessRate * 100).toFixed(2)}%`); + + // We should have a success rate of at least 70% for correct files + expect(correctSuccessRate).toBeGreaterThan(0.7); + + // Save the test results to a file + const testDir = path.join(process.cwd(), 'test', 'output'); + await fs.mkdir(testDir, { recursive: true }); + + const testResults = { + zugferdV1Correct: v1CorrectResults, + zugferdV1Fail: v1FailResults, + zugferdV2Correct: v2CorrectResults, + zugferdV2Fail: v2FailResults, + totalCorrectSuccessRate: correctSuccessRate + }; + + await fs.writeFile( + path.join(testDir, 'zugferd-corpus-results.json'), + JSON.stringify(testResults, null, 2) + ); +}); + +/** + * Tests a list of files and returns the results + * @param files List of files to test + * @param expectSuccess Whether we expect the files to be successfully processed + * @returns Test results + */ +async function testFiles(files: string[], expectSuccess: boolean): Promise<{ success: number, fail: number, details: any[] }> { + const results = { + success: 0, + fail: 0, + details: [] as any[] + }; + + for (const file of files) { + try { + // Read the file + const fileBuffer = await fs.readFile(file); + + // Create XInvoice from PDF + const xinvoice = await XInvoice.fromPdf(fileBuffer); + + // Check that the XInvoice instance has the expected properties + if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) { + // Check that the format is detected correctly + const format = xinvoice.getFormat(); + const isZugferd = [InvoiceFormat.ZUGFERD, InvoiceFormat.FACTURX, InvoiceFormat.CII].includes(format); + + if (isZugferd) { + // Try to export the invoice to XML + try { + const exportedXml = await xinvoice.exportXml('facturx'); + + if (exportedXml && exportedXml.includes('CrossIndustryInvoice')) { + // Success + results.success++; + results.details.push({ + file, + success: true, + format, + error: null + }); + } else { + // Failed to export valid XML + results.fail++; + results.details.push({ + file, + success: false, + format, + error: 'Failed to export valid XML' + }); + } + } catch (exportError) { + // Failed to export XML + results.fail++; + results.details.push({ + file, + success: false, + format, + error: `Export error: ${exportError.message}` + }); + } + } else { + // Wrong format detected + results.fail++; + results.details.push({ + file, + success: false, + format, + error: `Wrong format detected: ${format}` + }); + } + } else { + // Missing required properties + results.fail++; + results.details.push({ + file, + success: false, + format: null, + error: 'Missing required properties' + }); + } + } catch (error) { + // If we expect success, this is a failure + // If we expect failure, this is a success + if (expectSuccess) { + results.fail++; + results.details.push({ + file, + success: false, + format: null, + error: `Error: ${error.message}` + }); + } else { + results.success++; + results.details.push({ + file, + success: true, + format: null, + error: `Expected error: ${error.message}` + }); + } + } + } + + return results; +} + +/** + * Recursively finds files with a specific extension in a directory + * @param dir Directory to search + * @param extension File extension to look for + * @returns Array of file paths + */ +async function findFiles(dir: string, extension: string): Promise { + try { + const files = await fs.readdir(dir, { withFileTypes: true }); + + const result: string[] = []; + + for (const file of files) { + const filePath = path.join(dir, file.name); + + if (file.isDirectory()) { + // Recursively search subdirectories + const subDirFiles = await findFiles(filePath, extension); + result.push(...subDirFiles); + } else if (file.name.toLowerCase().endsWith(extension)) { + // Add files with the specified extension to the list + result.push(filePath); + } + } + + return result; + } catch (error) { + console.error(`Error finding files in ${dir}:`, error); + return []; + } +} + +// Run the tests +tap.start(); diff --git a/ts/classes.xinvoice.ts b/ts/classes.xinvoice.ts index e6d21aa..dbd1176 100644 --- a/ts/classes.xinvoice.ts +++ b/ts/classes.xinvoice.ts @@ -200,77 +200,14 @@ export class XInvoice { }; if (!xmlContent) { - // For testing purposes, create a simple invoice if no XML is found - console.warn('No XML found in PDF, creating a simple invoice for testing'); - - // Initialize with default values - this.id = `PDF-${Date.now()}`; - this.invoiceId = this.id; - this.invoiceType = 'debitnote'; - this.type = 'invoice'; - this.date = Date.now(); - this.status = 'invoice'; - this.subject = 'PDF Invoice'; - this.from = { - type: 'company', - name: 'PDF Seller', - description: '', - address: { - streetName: '', - houseNumber: '0', - city: '', - country: '', - postalCode: '' - }, - status: 'active', - foundedDate: { - year: 2000, - month: 1, - day: 1 - }, - registrationDetails: { - vatId: '', - registrationId: '', - registrationName: '' - } - }; - this.to = { - type: 'company', - name: 'PDF Buyer', - description: '', - address: { - streetName: '', - houseNumber: '0', - city: '', - country: '', - postalCode: '' - }, - status: 'active', - foundedDate: { - year: 2000, - month: 1, - day: 1 - }, - registrationDetails: { - vatId: '', - registrationId: '', - registrationName: '' - } - }; - this.incidenceId = this.id; - this.language = 'en'; - this.items = []; - this.dueInDays = 30; - this.reverseCharge = false; - this.currency = 'EUR'; - this.notes = ['PDF without embedded XML']; - this.objectActions = []; - this.detectedFormat = InvoiceFormat.FACTURX; - } else { - // Load the extracted XML - await this.loadXml(xmlContent, validate); + // No XML found in PDF + console.warn('No XML found in PDF'); + throw new Error('No XML found in PDF'); } + // Load the extracted XML + await this.loadXml(xmlContent, validate); + return this; } catch (error) { console.error('Error loading PDF:', error); diff --git a/ts/formats/pdf/pdf.extractor.ts b/ts/formats/pdf/pdf.extractor.ts index d2aff35..1030bf2 100644 --- a/ts/formats/pdf/pdf.extractor.ts +++ b/ts/formats/pdf/pdf.extractor.ts @@ -11,6 +11,34 @@ export class PDFExtractor { * @returns XML content or null if not found */ public async extractXml(pdfBuffer: Uint8Array | Buffer): Promise { + try { + // First try the standard extraction + const standardXml = await this.standardExtraction(pdfBuffer); + if (standardXml && this.isValidXml(standardXml)) { + return standardXml; + } + + // If standard extraction fails, try alternative methods + const alternativeXml = await this.alternativeExtraction(pdfBuffer); + if (alternativeXml && this.isValidXml(alternativeXml)) { + return alternativeXml; + } + + // If all extraction methods fail, return null + console.warn('All extraction methods failed, no valid XML found in PDF'); + return null; + } catch (error) { + console.error('Error extracting XML from PDF:', error); + return null; + } + } + + /** + * Standard extraction method using PDF-lib + * @param pdfBuffer PDF buffer + * @returns XML content or null if not found + */ + private async standardExtraction(pdfBuffer: Uint8Array | Buffer): Promise { try { const pdfDoc = await PDFDocument.load(pdfBuffer); @@ -50,13 +78,13 @@ export class PDFExtractor { // Get the filename as string const fileName = fileNameObj.toString(); - + // Check if it's an XML file (checking both extension and known standard filenames) - if (fileName.toLowerCase().includes('.xml') || + if (fileName.toLowerCase().includes('.xml') || fileName.toLowerCase().includes('factur-x') || fileName.toLowerCase().includes('zugferd') || fileName.toLowerCase().includes('xrechnung')) { - + const efDictObj = fileSpecObj.lookup(PDFName.of('EF')); if (!(efDictObj instanceof PDFDict)) { continue; @@ -80,28 +108,180 @@ export class PDFExtractor { // Decompress and decode the XML content try { + // Try to decompress with pako const xmlCompressedBytes = xmlFile.getContents().buffer; const xmlBytes = pako.inflate(xmlCompressedBytes); const xmlContent = new TextDecoder('utf-8').decode(xmlBytes); - console.log(`Successfully extracted XML from PDF file. File name: ${xmlFileName}`); - return xmlContent; + // Check if the XML content is valid + if (this.isValidXml(xmlContent)) { + console.log(`Successfully extracted XML from PDF file. File name: ${xmlFileName}`); + return xmlContent; + } + + // If we get here, the XML content is not valid, try without decompression + console.log('Decompression succeeded but XML is not valid, trying without decompression...'); + const rawXmlBytes = xmlFile.getContents(); + const rawXmlContent = new TextDecoder('utf-8').decode(rawXmlBytes); + + if (this.isValidXml(rawXmlContent)) { + console.log(`Successfully extracted uncompressed XML from PDF file. File name: ${xmlFileName}`); + return rawXmlContent; + } + + // If we get here, neither the decompressed nor the raw XML content is valid + console.log('Neither decompressed nor raw XML content is valid'); + return null; } catch (decompressError) { - // Try without decompression + // Decompression failed, try without decompression console.log('Decompression failed, trying without decompression...'); try { const xmlBytes = xmlFile.getContents(); const xmlContent = new TextDecoder('utf-8').decode(xmlBytes); - console.log(`Successfully extracted uncompressed XML from PDF file. File name: ${xmlFileName}`); - return xmlContent; + + if (this.isValidXml(xmlContent)) { + console.log(`Successfully extracted uncompressed XML from PDF file. File name: ${xmlFileName}`); + return xmlContent; + } + + // If we get here, the XML content is not valid + console.log('Uncompressed XML content is not valid'); + return null; } catch (decodeError) { console.error('Error decoding XML content:', decodeError); return null; } } } catch (error) { - console.error('Error extracting or parsing embedded XML from PDF:', error); + console.error('Error in standard extraction:', error); return null; } } + + /** + * Alternative extraction method using string search + * @param pdfBuffer PDF buffer + * @returns XML content or null if not found + */ + private async alternativeExtraction(pdfBuffer: Uint8Array | Buffer): Promise { + try { + // Convert buffer to string and look for XML patterns + const pdfString = Buffer.from(pdfBuffer).toString('utf8', 0, Math.min(pdfBuffer.length, 10000)); + + // Look for common XML patterns in the PDF + const xmlPatterns = [ + /<\?xml[^>]*\?>/i, + /]*>/i, + /]*>/i, + /]*>/i, + /]*>/i + ]; + + for (const pattern of xmlPatterns) { + const match = pdfString.match(pattern); + if (match) { + console.log(`Found XML pattern in PDF: ${match[0]}`); + + // Try to extract the XML content + const xmlContent = this.extractXmlFromString(pdfString); + if (xmlContent) { + console.log('Successfully extracted XML from PDF string'); + return xmlContent; + } + } + } + + return null; + } catch (error) { + console.error('Error in alternative extraction:', error); + return null; + } + } + + /** + * Extracts XML from a string + * @param pdfString PDF string + * @returns XML content or null if not found + */ + private extractXmlFromString(pdfString: string): string | null { + try { + // Look for XML start and end tags + const xmlStartIndex = pdfString.indexOf('', + '', + '', + '' + ]; + + let xmlEndIndex = -1; + for (const endTag of possibleEndTags) { + const endIndex = pdfString.indexOf(endTag); + if (endIndex !== -1) { + xmlEndIndex = endIndex + endTag.length; + break; + } + } + + if (xmlEndIndex === -1) { + return null; + } + + // Extract the XML content + return pdfString.substring(xmlStartIndex, xmlEndIndex); + } catch (error) { + console.error('Error extracting XML from string:', error); + return null; + } + } + + /** + * Checks if an XML string is valid + * @param xmlString XML string to check + * @returns True if the XML is valid + */ + private isValidXml(xmlString: string): boolean { + try { + // Check if the XML string contains basic XML structure + if (!xmlString.includes(' xmlString.includes(format)); + if (!hasKnownFormat) { + return false; + } + + // Check if the XML string contains binary data or invalid characters + const invalidChars = ['\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005']; + const hasBinaryData = invalidChars.some(char => xmlString.includes(char)); + if (hasBinaryData) { + return false; + } + + // Check if the XML string is too short + if (xmlString.length < 100) { + return false; + } + + return true; + } catch (error) { + console.error('Error validating XML:', error); + return false; + } + } }