مقال تقني

التحقق من صحة الفواتير الإلكترونية: veraPDF و Mustang في دلفي (Delphi)

فاتورة Factur-X أو ZUGFeRD عبارة عن مستندين يحملان اسم ملف واحد. المستند الخارجي عبارة عن حاوية PDF/A-3 يجب أن يقبلها قارئ الأرشفة للسنوات العشر القادمة. والمستند الداخلي عبارة عن فاتورة XML يجب أن يحللها نظام المحاسبة الخاص بالمشتري مقابل المعيار EN 16931. الخطأ الذي يؤدي إلى إرسال فواتير معطلة إلى الإنتاج هو الاعتقاد بأن تصحيح الأول يضمن صحة الثاني مجانًا. هذا غير صحيح. يمكن أن يكون الملف بتنسيق PDF/A-3 خاليًا من العيوب ولا يزال يحمل ملف XML لن تقبله أي مصلحة ضريبية، ويمكن أن يحمل ملف XML مثاليًا متوافقًا مع EN 16931 داخل حاوية تفشل في التحقق من الأرشفة. يتم التحقق من الطبقتين بواسطة أداتين مختلفتين لا تعرفان شيئًا عن بعضهما البعض، ويجب أن يرضي مسار العمل (pipeline) الحقيقي كليهما

أداتان للتحقق، وسؤالان مختلفان

تعد veraPDF التنفيذ المرجعي لـ PDF/A. عند توجيهها إلى فاتورة، فإنها تجيب على سؤال واحد: هل هذا ملف PDF/A-3 متوافق؟ فهي تتحقق من الأشياء التي يهتم بها معيار ISO 19005-3. هل كل خط مضمن؟ هل يوجد OutputIntent؟ هل تعلن بيانات XMP الوصفية عن الجزء ومستوى التوافق الصحيحين؟ بالنسبة للفاتورة الإلكترونية، فإنها تتحقق أيضًا من بنية الملفات المرتبطة التي يتطلبها PDF/A-3، لأن XML ينتقل كملف مضمن مع /AFRelationship وإدخال في مصفوفة /AF الخاصة بكتالوج المستند. لا تقول veraPDF شيئًا عما إذا كان إجمالي الفاتورة صحيحًا، لأن ذلك ليس من اختصاصها

برنامج Mustang هو أداة تحقق مفتوحة المصدر من Mustangproject. وهو يطرح سؤالاً مستقلاً: هل ملف XML المضمن عبارة عن فاتورة صالحة؟ حيث يقوم بتشغيل XML مقابل المخطط (schema) للملف الشخصي (profile) المعلن ثم يطبق قواعد العمل الخاصة بـ EN 16931 ومجموعات القواعد الخاصة بكل بلد والمتراكبة فوقها، ومن بينها CIUS الخاصة بـ XRechnung. يتحقق مما إذا كان معرف ضريبة القيمة المضافة للبائع موجودًا عندما تتطلب الإجماليات ذلك، ومن أن مبالغ البدل والرسوم تتطابق مع إجمالي المستند، ومن أن URN للملف الشخصي في XML يطابق ما يدعيه الملف. لا يهتم Mustang بما إذا كان ملف PDF المحيط يضمن خطوطه، لأن هذه هي وظيفة veraPDF

لا تعتبر أي من الأداتين مجموعة شاملة للأخرى. تمرر veraPDF حاوية مثالية هيكليًا حول ملف XML لا معنى له. ويمرر Mustang ملف XML مثاليًا مغلفًا في حاوية تفتقر إلى OutputIntent. تكتشف كل منهما بالضبط فئة العيوب التي لا تراها الأخرى، وهذا هو السبب الكامل الذي يجعل بيئة التحقق الجادة تقوم بتشغيل كلتيهما وتعتبر الملف جاهزًا للإرسال فقط عندما تتفق كلتاهما

مصفوفة التحقق (The validation matrix)

لإثبات أن المكتبة تنتج ملفات تتجاوز كلا البوابتين، تبني بيئة الاختبار مصفوفة. تغطي ستة ملفات شخصية للفواتير النطاق الذي يواجهه مسار العمل الأوروبي في الممارسة العملية: Factur-X EN 16931 و Factur-X BASIC ومتغير Factur-X EXTENDED France B2B و XRechnung 3.0 و ZUGFeRD 1.0 COMFORT و ZUGFeRD 2.0 BASIC. يتم إنشاء كل ملف شخصي مقابل مستويين فرعيين لتوافق PDF/A، وهما 3b و 3u، لأن متطلبات المستوى B والمستوى U تختلف في تعيين Unicode، والملف الذي يجتاز أحدهما يمكن أن يفشل في الآخر. ستة ملفات شخصية مضروبة في مستويين تساوي اثني عشر ملفًا، تم بناء كل منها بدون واجهة رسومية (headless) بواسطة نفس مسار الكود الذي يتم شحنه في نموذج واجهة المستخدم الرسومية، لذا فإن العناصر قيد الاختبار لم يتم ضبطها يدويًا للاختبار

يقوم المولد بكتابة الاثني عشر ملفًا، ويقوم برنامج نصي بتغذية كل منها إلى كلتا أداتي التحقق. في أول تشغيل كامل، نجحت veraPDF في تمرير جميع الاثني عشر. كانت بنية الحاوية صحيحة في جميع المجالات: تم تسجيل الملفات المرتبطة، وتم الإعلان عن توافق XMP، ووضعت نوايا المخرجات (output intents) في مكانها. نجح Mustang في تمرير ثمانية. كانت أربع فواتير عبارة عن ملفات PDF/A-3 صالحة هيكليًا تحمل ملفات XML رفضتها أداة التحقق من قواعد العمل، وهو بالضبط الانقسام الذي وُجد نهج الأداتين لإظهاره. لو وثقت بيئة الاختبار في veraPDF وحدها، لكانت هذه الفواتير الأربع قد بدت مكتملة

الإصلاحان اللذان سدا الفجوة

جاءت إخفاقات Mustang الأربعة من سببين مختلفين، وإصلاح كل منهما هو تفصيل يستحق المعرفة قبل أن تقوم بإنشاء هذه الملفات الشخصية بنفسك

كان الأول هو الملف الشخصي Factur-X EXTENDED France B2B. مرر المولد الأصلي تسمية داخلية كمستوى التوافق و URN داخلي كمبدأ توجيهي، ورفض Mustang الملف مع خطأ في قيمة التوافق غير الصالحة متبوعًا بخطأ نوع الملف الشخصي غير المدعوم. والسبب هو أن حقل fx:ConformanceLevel في XMP ليس فتحة نص حر لتسمية ملفك الشخصي. يحدد Factur-X خمس قيم قياسية له بالضبط: MINIMUM و BASIC WL و BASIC و EN 16931 و EXTENDED. لا تزال فاتورة B2B الخاصة بفرنسا مستندًا بملف شخصي EXTENDED فيما يتعلق ببيانات XMP الوصفية. لا يتم التعبير عن الطابع الفرنسي للفاتورة عن طريق اختراع قيمة توافق سادسة. بل يتم التعبير عنه بواسطة رمز البلد، FR، وبواسطة معرف المبدأ التوجيهي داخل XML، والذي يجب أن يحمل البادئة urn:cen.eu:en16931:2017#conformant# التي تميز CIUS المتوافق مع EN 16931. تمرير القيمة القياسية EXTENDED مع FR كرمز بلد و URN المبدأ التوجيهي الصحيح جعل الملف متوافقًا

في واجهة برمجة تطبيقات (API) المكتبة، يعد هذا استدعاء لـ AddFacturXAssociatedFileFromString مع محاذاة التوافق والبلد والمبدأ التوجيهي. تحمل وسيطة مستوى التوافق الرمز القياسي، وتحمل وسيطة رمز البلد FR، ويوجد URN المبدأ التوجيهي في بايتات 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 مقابل :1p0 XSD، والذي يعتبر أكثر صرامة بشأن عدد العناصر (cardinality) مما تشير إليه الملخصات النثرية. يتطلب XSD أن يحتوي تجميع تسوية الرأس، ram:SpecifiedTradeSettlementMonetarySummation، على ram:ChargeTotalAmount و ram:AllowanceTotalAmount مرة واحدة بالضبط لكل منهما. حذف ملف XML الذي تم إنشاؤه كليهما، لذلك أبلغ Mustang أن العناصر يجب أن تحدث مرة واحدة بالضبط. هذه ليست اختيارية عندما يقول المخطط أن minOccurs هو واحد. أدى إصدار كليهما بترتيب تسلسل XSD، مباشرة بعد ram:LineTotalAmount، بقيمة 0.00 عندما لا توجد رسوم أو بدلات، إلى استيفاء المخطط. الصفر هو عنصر موجود؛ والعنصر الغائب هو انتهاك للمخطط. مع وجود هذين الإصلاحين، ارتفعت المصفوفة إلى اثني عشر من أصل اثني عشر على Mustang بينما بقيت اثني عشر من أصل اثني عشر على veraPDF

حقول XRechnung التي تقلب حالة الملف من غير صالح إلى صالح

تستحق XRechnung ملاحظة خاصة لأن CIUS الألماني الخاص بها يضيف قواعد عمل غائبة عن مجموعة EN 16931 الأساسية، وتفشل بطرق تبدو وكأن لا شيء خطأ في المستند للوهلة الأولى. يتعلق اثنان منها بالعناوين الإلكترونية. BT-34 هو العنوان الإلكتروني للبائع و BT-49 هو العنوان الإلكتروني للمشتري، وهما نقطتا نهاية التوجيه التي تستخدمها بوابة القطاع العام الألماني لتسليم الفاتورة والإقرار بها. يعامل نموذج EN 16931 الأساسي هذه العناصر كاختيارية. بينما XRechnung لا تفعل ذلك. إذا حذفت أيهما، فستكون الفاتورة جيدة التكوين، وصالحة المخطط، ومرفوضة

الثالث هو القاعدة BR-DE-6، التي تتطلب وجود رقم هاتف الاتصال الخاص بالبائع. إنه نوع الحقل الذي يسقطه المطور لأنه يبدو وكأنه عرض تقديمي وليس بيانات، وغيابه ينتج عنه فشل في التحقق يشير إلى مجموعة اتصال البائع بدلاً من أي شيء مفقود بوضوح. توفير BT-34 و BT-49 ورقم هاتف البائع هو ما ينقل ملف XRechnung من غير صالح إلى صالح ضمن Mustang، ولا يغير أي من ذلك أي شيء تراه veraPDF، لأن الثلاثة تعيش في XML

ربط مخرجات المكتبة بأداة تحقق

تُعمم النقطة المعمارية وراء بيئة الاختبار على أي نظام عمل. تقوم مكتبة PDF بكتابة حاوية متوافقة وتضمين XML. وهي لا تحاول، ولا ينبغي لها، أن تكون المرجع لقواعد العمل EN 16931. تتحقق وظيفة ValidateFacturXInvoice في المكتبة من اتساق الحاوية، ومن أن مصفوفة /AF الخاصة بالكتالوج، وشجرة أسماء الملفات المضمنة، و DocumentFileName في XMP، والملف الشخصي، والمبدأ التوجيهي، و /AFRelationship تتفق جميعها، لكنها لا تتحقق من الرموز الضريبية أو تطابق المبالغ. التقسيم الصحيح للعمل هو أن يقوم نظام العمل باستخراج XML وتسليمه إلى أداة تحقق مخصصة للفواتير، تمامًا كما تسلمه بيئة الاختبار إلى Mustang

قراءة الملف مرة أخرى تخبرك بما تمت كتابته بالفعل. تبلغ وظيفة DetectFacturXInvoice عما إذا كان قد تم التعرف على الفاتورة، وتقرأ GetFacturXInvoiceInfo حقول البيانات الوصفية حسب العلامة (tag): العلامة 1 هي اسم الملف المضمن، والعلامة 2 هي DocumentFileName في XMP، والعلامة 5 هي مستوى التوافق، والعلامة 6 هي معرف المبدأ التوجيهي، والعلامة 7 هي /AFRelationship. التأكد من أن مستوى التوافق الذي تقرأه هو الرمز القياسي وليس تسمية داخلية هو أرخص طريقة لالتقاط خطأ 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 بايتات XML الخام كـ AnsiString، جاهزة للكتابة في ملف أو تدفق (stream) إلى عملية التحقق. في بيئة الاختبار، يكون الهدف هو Mustang، الذي يتم استدعاؤه من خلال ملف jar الخاص بسطر الأوامر، مع تشغيل veraPDF في نفس التمريرة على نفس الملف. التوصيل بسيط: مولد وحدة تحكم (console)، EInvoiceValidation.dpr، يكتب الاثني عشر ملفًا باستخدام نموذج الفاتورة المشترك من العينة، وبرنامج نصي، run-validation.ps1، يوجه كلتا أداتي التحقق عبر دليل الإخراج ويطبع جدول النجاح والفشل. نفس الشكل المكون من خطوتين، التوليد باستخدام المكتبة والتحقق باستخدام أدوات تحقق خارجية، هو ما يجب أن تقوم به مهمة التكامل المستمر (continuous-integration) عند كل تغيير في إنشاء الفاتورة، لأن الطريقة الوحيدة لمعرفة أن الملف يرضي كلتا الطبقتين هي سؤال كلتا الأداتين

إذا كان مسار العمل الخاص بك يحتاج أيضًا إلى اعتماد (certify) الحاوية قبل التوقيع، فإن جانب الفحص المسبق (preflight) من هذا العمل مغطى في دليلنا التفصيلي للفحص المسبق لـ PDF/A و PDF/UA في دلفي، كما تم وصف تدفق الاعتماد ثم التوقيع الأوسع في منصة الامتثال والتوقيع (compliance and signing workbench). كلاهما يعتمد على نفس مسار التوليد الذي يتم شحنه كجزء من مكتبة Delphi PDF لـ Delphi و C++Builder، جنبًا إلى جنب مع واجهات برمجة التطبيقات لـ PDF/A والملفات المرتبطة والبيانات الوصفية المستخدمة هنا