Ένα τιμολόγιο Factur-X ή ZUGFeRD είναι δύο έγγραφα που φορούν ένα όνομα αρχείου. Το εξωτερικό έγγραφο είναι ένα κοντέινερ PDF/A-3 που πρέπει να αποδεχτεί ένας αρχειακός αναγνώστης (archival reader) για τα επόμενα δέκα χρόνια. Το εσωτερικό έγγραφο είναι ένα τιμολόγιο XML που πρέπει να αναλύσει (parse) το λογιστικό σύστημα (accounting system) ενός αγοραστή σε σχέση με το (against) EN 16931. Το λάθος που στέλνει (ships) σπασμένα τιμολόγια στην παραγωγή είναι η πεποίθηση ότι το να κάνετε σωστό το πρώτο σημαίνει ότι παίρνετε το δεύτερο δωρεάν. Δεν ισχύει. Ένα αρχείο μπορεί να είναι ένα άψογο (flawless) PDF/A-3 και παρόλα αυτά να φέρει XML που καμία φορολογική αρχή δεν θα αποδεχτεί, και μπορεί να φέρει XML του EN 16931 που αποτελεί παράδειγμα προς μίμηση (textbook) μέσα σε ένα κοντέινερ που αποτυγχάνει στην αρχειακή επικύρωση (archival validation). Τα δύο στρώματα (layers) επικυρώνονται (validated) από δύο διαφορετικά εργαλεία που δεν γνωρίζουν τίποτα το ένα για το άλλο, και μια πραγματική ροή (pipeline) πρέπει να ικανοποιεί και τα δύο
Δύο επικυρωτές, δύο διαφορετικές ερωτήσεις (Two validators, two different questions)
Το veraPDF είναι η υλοποίηση αναφοράς (reference implementation) για το PDF/A. Δείξτε του ένα τιμολόγιο και απαντά σε μία ερώτηση: είναι αυτό ένα συμμορφούμενο (conformant) αρχείο PDF/A-3. Ελέγχει τα πράγματα για τα οποία νοιάζεται το ISO 19005-3. Είναι ενσωματωμένη (embedded) κάθε γραμματοσειρά. Υπάρχει OutputIntent (πρόθεση εξόδου); Δηλώνουν τα μεταδεδομένα XMP το σωστό μέρος (part) και επίπεδο συμμόρφωσης (conformance level). Για ένα ηλεκτρονικό τιμολόγιο ελέγχει επίσης τις υδραυλικές εγκαταστάσεις (plumbing) των συσχετισμένων αρχείων (associated-file) που απαιτεί το PDF/A-3, επειδή το XML ταξιδεύει μαζί (rides along) ως ενσωματωμένο αρχείο με μια /AFRelationship και μια καταχώριση στον πίνακα /AF του καταλόγου του εγγράφου. Το veraPDF δεν λέει τίποτα για το αν το σύνολο του τιμολογίου βγάζει νόημα (adds up), επειδή αυτό δεν ανήκει στην αρμοδιότητά του (remit)
Το Mustang είναι ο επικυρωτής ανοιχτού κώδικα (open-source validator) από το Mustangproject. Κάνει την ορθογώνια (orthogonal) ερώτηση: είναι το ενσωματωμένο XML ένα έγκυρο τιμολόγιο; Εκτελεί το XML σε σχέση με το σχήμα (schema) για το δηλωμένο προφίλ (profile) και στη συνέχεια εφαρμόζει τους επιχειρηματικούς κανόνες (business rules) του EN 16931 και τα σύνολα κανόνων (rule sets) ανά χώρα που επιστρώνονται (layered) από πάνω, μεταξύ αυτών το CIUS της XRechnung. Ελέγχει ότι υπάρχει αναγνωριστικό ΦΠΑ (VAT identifier) πωλητή όταν τα σύνολα το απαιτούν, ότι τα ποσά των εκπτώσεων (allowance) και των χρεώσεων (charge) συμβιβάζονται (reconcile) με το σύνολο του εγγράφου, ότι το URN του προφίλ στο XML ταιριάζει με αυτό που ισχυρίζεται ότι είναι το αρχείο. Το Mustang δεν νοιάζεται αν το περιβάλλον PDF ενσωματώνει τις γραμματοσειρές του, επειδή αυτή είναι η δουλειά του veraPDF
Κανένα από τα δύο εργαλεία δεν είναι υπερσύνολο του άλλου. Το veraPDF εγκρίνει (passes) ένα δομικά τέλειο (structurally perfect) κοντέινερ γύρω από XML χωρίς νόημα. Το Mustang εγκρίνει τέλειο XML τυλιγμένο (wrapped) σε ένα κοντέινερ με OutputIntent που λείπει. Κάθε ένα πιάνει ακριβώς την κατηγορία (class) ελαττωμάτων (defect) στην οποία το άλλο είναι τυφλό, πράγμα που είναι ολόκληρος ο λόγος (entire reason) που μια σοβαρή υποδομή (harness) επικύρωσης εκτελεί και τα δύο και αντιμετωπίζει ένα αρχείο ως αποστολίσιμο (shippable) μόνο όταν συμφωνούν και τα δύο
Η μήτρα επικύρωσης (The validation matrix)
Για να αποδείξει (prove) ότι η βιβλιοθήκη παράγει αρχεία που επιβιώνουν και από τις δύο πύλες (gates), η υποδομή (harness) χτίζει μια μήτρα (matrix). Έξι προφίλ τιμολογίων καλύπτουν το εύρος (range) που συναντά στην πράξη μια ευρωπαϊκή ροή (pipeline): Factur-X EN 16931, Factur-X BASIC, η παραλλαγή (variant) Factur-X EXTENDED France B2B, XRechnung 3.0, ZUGFeRD 1.0 COMFORT, και ZUGFeRD 2.0 BASIC. Κάθε προφίλ δημιουργείται σε σχέση με δύο υποεπίπεδα συμμόρφωσης (sub-conformance levels) του PDF/A, τα 3b και 3u, επειδή οι απαιτήσεις του επιπέδου B και του επιπέδου U αποκλίνουν (diverge) στη χαρτογράφηση (mapping) Unicode και ένα αρχείο που περνάει το ένα μπορεί να αποτύχει στο άλλο. Έξι προφίλ επί δύο επίπεδα μας κάνουν δώδεκα αρχεία, το καθένα από τα οποία δημιουργείται χωρίς διεπαφή (headless) από την ίδια διαδρομή κώδικα (code path) που αποστέλλει (ships) το δείγμα (sample) GUI, επομένως τα τεχνουργήματα (artifacts) υπό δοκιμή δεν ρυθμίζονται με το χέρι (hand-tuned) για τη δοκιμή (test)
Η γεννήτρια (generator) γράφει και τα δώδεκα και ένα σενάριο (script) τροφοδοτεί (feeds) το καθένα και στους δύο επικυρωτές. Στην πρώτη πλήρη εκτέλεση (run), το veraPDF ενέκρινε (passed) και τα δώδεκα. Οι υδραυλικές εγκαταστάσεις (plumbing) του κοντέινερ ήταν σωστές σε όλους τους τομείς (across the board): τα συσχετισμένα αρχεία είχαν καταχωρηθεί (registered), η συμμόρφωση XMP είχε δηλωθεί (declared), τα output intents ήταν στη θέση τους. Το Mustang ενέκρινε οκτώ. Τέσσερα τιμολόγια ήταν δομικά έγκυρα αρχεία PDF/A-3 που έφεραν XML το οποίο απέρριψε (rejected) ο επικυρωτής επιχειρηματικών κανόνων (business-rule validator), που είναι ακριβώς ο διαχωρισμός (split) που η προσέγγιση των δύο εργαλείων (two-tool approach) υπάρχει για να φέρει στην επιφάνεια (surface). Αν η υποδομή (harness) είχε εμπιστευτεί μόνο το veraPDF, αυτά τα τέσσερα θα φαίνονταν ολοκληρωμένα
Οι δύο διορθώσεις (fixes) που έκλεισαν το κενό (gap)
Οι τέσσερις αποτυχίες του Mustang προήλθαν από δύο διακριτές (distinct) αιτίες, και η διόρθωση (fix) για την καθεμία είναι μια λεπτομέρεια που αξίζει να γνωρίζετε πριν δημιουργήσετε αυτά τα προφίλ (profiles) μόνοι σας
Το πρώτο ήταν το προφίλ Factur-X EXTENDED France B2B. Η αρχική γεννήτρια πέρασε μια εσωτερική ετικέτα (internal label) ως το επίπεδο συμμόρφωσης (conformance level) και ένα εσωτερικό URN ως την κατευθυντήρια γραμμή (guideline), και το Mustang απέρριψε το αρχείο με ένα σφάλμα μη έγκυρης-τιμής-συμμόρφωσης (invalid-conformance-value) ακολουθούμενο από ένα σφάλμα μη υποστηριζόμενου-τύπου-προφίλ (unsupported-profile-type). Ο λόγος είναι ότι το πεδίο fx:ConformanceLevel του XMP δεν είναι μια υποδοχή (slot) ελεύθερου κειμένου για τη δική σας ονομασία προφίλ. Το Factur-X ορίζει ακριβώς πέντε τυπικές (standard) τιμές για αυτό: MINIMUM, BASIC WL, BASIC, EN 16931, και EXTENDED. Ένα τιμολόγιο B2B ειδικά για τη Γαλλία εξακολουθεί να είναι ένα έγγραφο προφίλ EXTENDED όσον αφορά τα μεταδεδομένα XMP. Ο γαλλικός χαρακτήρας του τιμολογίου δεν εκφράζεται εφευρίσκοντας μια έκτη τιμή συμμόρφωσης. Εκφράζεται από τον κωδικό χώρας, FR, και από το αναγνωριστικό της κατευθυντήριας γραμμής (guideline identifier) μέσα στο XML, το οποίο πρέπει να φέρει το πρόθεμα (prefix) urn:cen.eu:en16931:2017#conformant# που σηματοδοτεί (marks) ένα CIUS συμμορφούμενο (conformant) με το EN 16931. Περνώντας την τυπική τιμή EXTENDED με το FR ως κωδικό χώρας και το σωστό URN της κατευθυντήριας γραμμής, το αρχείο έγινε συμμορφούμενο
Στο API της βιβλιοθήκης, αυτή είναι μια κλήση στην AddFacturXAssociatedFileFromString με τη συμμόρφωση (conformance), τη χώρα και την κατευθυντήρια γραμμή ευθυγραμμισμένα (aligned). Το όρισμα του επιπέδου συμμόρφωσης φέρει το τυπικό κουπόνι (standard token), το όρισμα του κωδικού χώρας φέρει FR, και το URN της κατευθυντήριας γραμμής βρίσκεται μέσα στα (lives in) bytes του XML που περνάτε
var
FileID: Integer;
begin
PDF.SetPDFAMode(5); // PDF/A-3b
PDF.NewDocument;
// ... draw the human-readable invoice page ...
// ExtendedXML carries an EN 16931 guideline URN of the form
// urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
FileID := PDF.AddFacturXAssociatedFileFromString(
ExtendedXML,
'EXTENDED', // standard fx:ConformanceLevel, not an internal label
'factur-x.xml',
'Factur-X EXTENDED invoice',
'Alternative', // /AFRelationship
'1.0',
'FR'); // France B2B marked by country code, not by conformance
if FileID = 0 then
raise Exception.Create('Factur-X attachment rejected');
PDF.SaveToFile('02_Factur-X-EXTENDED-FR_PDFA-3b.pdf');
end;
Η δεύτερη αιτία ήταν το προφίλ ZUGFeRD 1.0 COMFORT, και δεν είχε καμία σχέση με μεταδεδομένα. Το ZUGFeRD 1.0 επικυρώνεται (validated) σε σχέση με το XSD :1p0, το οποίο είναι πιο αυστηρό (stricter) σχετικά με την πληθικότητα (cardinality) από ό,τι υποδηλώνουν οι πεζογραφικές (prose) περιλήψεις (summaries). Το XSD απαιτεί το άθροισμα διακανονισμού της κεφαλίδας (header settlement summation), ram:SpecifiedTradeSettlementMonetarySummation, να περιέχει το ram:ChargeTotalAmount και το ram:AllowanceTotalAmount το καθένα ακριβώς μία φορά. Το παραγόμενο (generated) XML παρέλειψε και τα δύο, επομένως το Mustang ανέφερε ότι τα στοιχεία πρέπει να εμφανίζονται ακριβώς μία φορά. Αυτά δεν είναι προαιρετικά (optional) όταν το σχήμα (schema) λέει ότι το minOccurs είναι ένα. Εκπέμποντας (emitting) και τα δύο με σειρά ακολουθίας του XSD (XSD sequence order), αμέσως μετά το ram:LineTotalAmount, με τιμή 0.00 όταν δεν υπάρχουν χρεώσεις (charges) ή εκπτώσεις (allowances), ικανοποίησε το σχήμα. Ένα μηδέν είναι ένα παρόν στοιχείο (present element)· ένα απόν στοιχείο είναι παραβίαση (violation) του σχήματος. Με αυτές τις δύο διορθώσεις στη θέση τους, η μήτρα πήγε σε δώδεκα στα δώδεκα στο Mustang, ενώ παρέμεινε δώδεκα στα δώδεκα στο veraPDF
Τα πεδία της XRechnung που αλλάζουν (flip) το μη έγκυρο σε έγκυρο
Η XRechnung αξίζει τη δική της σημείωση επειδή το γερμανικό CIUS της προσθέτει επιχειρηματικούς κανόνες (business rules) που απουσιάζουν από το βασικό σύνολο του EN 16931, και αποτυγχάνουν με τρόπους που με την πρώτη ματιά (at a glance) φαίνεται σαν να μην υπάρχει τίποτα λάθος με το έγγραφο. Δύο από αυτούς αφορούν τις ηλεκτρονικές διευθύνσεις (electronic addresses). Το BT-34 είναι η ηλεκτρονική διεύθυνση του πωλητή (seller) και το BT-49 είναι η ηλεκτρονική διεύθυνση του αγοραστή (buyer), τα τελικά σημεία (endpoints) δρομολόγησης (routing) που χρησιμοποιεί μια γερμανική πύλη (portal) του δημόσιου τομέα (public-sector) για να παραδώσει (deliver) και να επιβεβαιώσει (acknowledge) το τιμολόγιο. Το βασικό μοντέλο του EN 16931 τα αντιμετωπίζει (treats) ως προαιρετικά. Η XRechnung όχι. Παραλείψτε (omit) οποιοδήποτε από τα δύο, και το τιμολόγιο είναι καλοσχηματισμένο (well-formed), έγκυρο ως προς το σχήμα (schema-valid), και απορρίπτεται
Ο τρίτος είναι ο κανόνας BR-DE-6, ο οποίος απαιτεί να υπάρχει ο αριθμός τηλεφώνου (contact telephone number) του πωλητή. Είναι το είδος του πεδίου που ένας προγραμματιστής (developer) ρίχνει (drops) επειδή μοιάζει με παρουσίαση (presentation) παρά με δεδομένα, και η απουσία του παράγει μια αποτυχία επικύρωσης που δείχνει την ομάδα επαφής (contact group) του πωλητή αντί για κάτι που προφανώς (obviously) λείπει. Η παροχή (supplying) του BT-34, του BT-49 και του αριθμού τηλεφώνου του πωλητή είναι αυτό που μετακινεί ένα αρχείο XRechnung από μη έγκυρο σε έγκυρο στο Mustang, και κανένα από αυτά δεν αλλάζει τίποτα από όσα βλέπει το veraPDF, επειδή και τα τρία βρίσκονται (live) στο XML
Σύνδεση (wiring) της εξόδου της βιβλιοθήκης σε έναν επικυρωτή (validator)
Το αρχιτεκτονικό σημείο (architectural point) πίσω από την υποδομή (harness) γενικεύεται (generalizes) σε οποιοδήποτε επιχειρηματικό σύστημα (business system). Η βιβλιοθήκη PDF γράφει ένα συμμορφούμενο (conformant) κοντέινερ και ενσωματώνει το XML. Δεν προσπαθεί, και δεν θα έπρεπε, να προσπαθήσει να αποτελέσει την αυθεντία (authority) των επιχειρηματικών κανόνων του EN 16931. Η ValidateFacturXInvoice στη βιβλιοθήκη ελέγχει τη συνέπεια (consistency) του κοντέινερ, ότι ο πίνακας /AF του καταλόγου, το δέντρο ονομάτων (name tree) των ενσωματωμένων αρχείων, το DocumentFileName στο XMP, το προφίλ, η κατευθυντήρια γραμμή (guideline), και το /AFRelationship συμφωνούν όλα μεταξύ τους, αλλά δεν επικυρώνει φορολογικούς κωδικούς (tax codes) ούτε συμβιβάζει (reconcile) ποσά. Ο σωστός καταμερισμός (division) εργασίας είναι το επιχειρηματικό σύστημα να εξάγει το XML και να το παραδώσει σε έναν αποκλειστικό (dedicated) επικυρωτή τιμολογίων, ακριβώς όπως η υποδομή το παραδίδει στο Mustang
Η ανάγνωση του αρχείου προς τα πίσω (reading the file back) σας λέει τι γράφτηκε πραγματικά. Η DetectFacturXInvoice αναφέρει εάν αναγνωρίστηκε ένα τιμολόγιο, και η GetFacturXInvoiceInfo διαβάζει τα πεδία (fields) των μεταδεδομένων ανά ετικέτα (tag): η ετικέτα 1 είναι το όνομα του ενσωματωμένου αρχείου, η ετικέτα 2 το DocumentFileName στο XMP, η ετικέτα 5 το επίπεδο συμμόρφωσης (conformance level), η ετικέτα 6 το αναγνωριστικό της κατευθυντήριας γραμμής, και η ετικέτα 7 η /AFRelationship. Η επιβεβαίωση ότι το επίπεδο συμμόρφωσης που διαβάζετε πίσω (read back) είναι το τυπικό κουπόνι (standard token) και όχι μια εσωτερική ετικέτα (internal label) είναι ο φθηνότερος (cheapest) τρόπος να εντοπίσετε το λάθος του EXTENDED πριν ένα αρχείο φύγει από το build (κατασκευή) σας
function ExtractAndInspect(const PdfPath: string): AnsiString;
var
Profile, Guideline: WideString;
begin
Result := '';
PDF.LoadFromFile(PdfPath);
if PDF.DetectFacturXInvoice = 1 then
begin
Profile := PDF.GetFacturXInvoiceInfo(5); // fx:ConformanceLevel
Guideline := PDF.GetFacturXInvoiceInfo(6); // XML guideline ID
Writeln('Profile: ', Profile);
Writeln('Guideline: ', Guideline);
// Hand the raw XML to a dedicated EN 16931 / Mustang validator.
Result := PDF.ExtractFacturXXMLToString;
end;
end;
Η ExtractFacturXXMLToString επιστρέφει τα ακατέργαστα (raw) bytes του XML ως AnsiString, έτοιμα να γραφτούν σε ένα αρχείο ή να διαβιβαστούν (stream) σε μια διεργασία (process) επικυρωτή. Στην υποδομή (harness) δοκιμών, αυτός ο στόχος (target) είναι το Mustang, το οποίο καλείται (invoked) μέσω του jar του στη γραμμή εντολών (command-line), με το veraPDF να εκτελείται (run) στο ίδιο πέρασμα (pass) πάνω από το ίδιο αρχείο. Η σύνδεση (wiring) είναι μικρή: μια γεννήτρια κονσόλας, η EInvoiceValidation.dpr, γράφει τα δώδεκα αρχεία χρησιμοποιώντας το κοινόχρηστο (shared) μοντέλο τιμολογίου (invoice model) από το δείγμα (sample), και ένα σενάριο (script), το run-validation.ps1, οδηγεί (drives) και τους δύο επικυρωτές στον κατάλογο εξόδου και τυπώνει έναν πίνακα επιτυχιών και αποτυχιών. Το ίδιο σχήμα (shape) δύο βημάτων, δημιουργία με τη βιβλιοθήκη και επαλήθευση (verify) με εξωτερικούς επικυρωτές (validators), είναι αυτό που θα έπρεπε να εκτελεί μια εργασία συνεχούς ενσωμάτωσης (continuous-integration job) σε κάθε αλλαγή στη δημιουργία τιμολογίων, επειδή ο μόνος τρόπος για να γνωρίζετε ότι ένα αρχείο ικανοποιεί (satisfies) και τα δύο στρώματα είναι να ρωτήσετε και τα δύο εργαλεία
Εάν η ροή (pipeline) σας πρέπει επίσης να πιστοποιήσει (certify) το κοντέινερ (container) πριν από την υπογραφή (signing), η πλευρά του preflight αυτής της εργασίας καλύπτεται στην αναλυτική παρουσίασή (walkthrough) μας του preflight του PDF/A και του PDF/UA στη Delphi, και η ευρύτερη (broader) ροή (flow) πιστοποίησης-έπειτα-υπογραφής (certify-then-sign) περιγράφεται στον πάγκο εργασίας (workbench) συμμόρφωσης και υπογραφής. Και τα δύο βασίζονται (build on) στην ίδια διαδρομή δημιουργίας (generation path) που αποστέλλεται (ships) ως μέρος της Βιβλιοθήκης PDF της Delphi (Delphi PDF Library) για Delphi και C++Builder, μαζί με τα API του PDF/A, των συσχετισμένων αρχείων (associated-file), και των μεταδεδομένων (metadata) που χρησιμοποιούνται εδώ