مقال تقني

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

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

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

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

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

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

مصفوفة التحقق من الصحة

لإثبات أن المكتبة تنتج ملفات تجتاز كلتا البوابتين، يبني نظام الاختبار مصفوفة. تغطي ستة ملفات شخصية للفواتير النطاق الذي يواجهه مسار العمل الأوروبي عملياً: 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 والملف الذي يجتاز أحدهما قد يفشل في الآخر. ستة ملفات شخصية مضروبة في مستويين تعني اثني عشر ملفاً، كل واحد منها يتم بناؤه بدون واجهة رسومية بواسطة نفس مسار الكود الذي يتم شحنه في نموذج واجهة المستخدم الرسومية، لذلك فإن العناصر قيد الاختبار لم يتم ضبطها يدوياً من أجل الاختبار

يقوم المولد بكتابة جميع الملفات الاثني عشر ويقوم برنامج نصي بتغذية كل منها لكلتا أداتي التحقق. في أول تشغيل كامل، اجتاز 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 الصحيح للمبدأ التوجيهي جعل الملف متوافقاً

في واجهة برمجة تطبيقات المكتبة، هذا عبارة عن استدعاء لـ 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 مقابل XSD للإصدار :1p0، وهو أكثر صرامة بشأن الأصلية (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 حقول البيانات الوصفية بواسطة العلامة: العلامة 1 هي اسم الملف المضمن، والعلامة 2 هي DocumentFileName لـ XMP، والعلامة 5 هي مستوى التوافق، والعلامة 6 هي مُعرّف المبدأ التوجيهي، والعلامة 7 هي /AFRelationship. التأكد من أن مستوى التوافق الذي تقرأه مرة أخرى هو الرمز القياسي وليس تسمية داخلية هو أرخص طريقة لاكتشاف خطأ EXTENDED قبل أن يغادر الملف البناء الخاص بك

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، لتكون جاهزة للكتابة في ملف أو تدفقها إلى عملية تحقق. في نظام الاختبار، هذا الهدف هو Mustang، والذي يتم استدعاؤه من خلال ملف jar الخاص بسطر الأوامر، مع تشغيل veraPDF في نفس التمريرة على نفس الملف. الربط بسيط: يقوم مُولد وحدة التحكم، EInvoiceValidation.dpr، بكتابة الملفات الاثني عشر باستخدام نموذج الفاتورة المشترك من العينة، ويقوم برنامج نصي، run-validation.ps1، بتوجيه كلتا أداتي التحقق على دليل الإخراج ويطبع جدولاً للنجاح والفشل. نفس الشكل المكون من خطوتين، التوليد باستخدام المكتبة والتحقق باستخدام أدوات تحقق خارجية، هو ما يجب أن تقوم به مهمة التكامل المستمر في كل تغيير لتوليد الفواتير، لأن الطريقة الوحيدة لمعرفة أن الملف يلبي كلتا الطبقتين هي سؤال كلتا الأداتين

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