مقال تقني

فواتير Factur-X و ZUGFeRD الهجينة في Delphi

الفاتورة الإلكترونية المتوافقة ليست ملف PDF مع ملف XML مرفق على الجانب. إنها مستند PDF/A-3 واحد يحمل الفاتورة مرتين: مرة كصفحة يقرأها الإنسان، ومرة كملف Cross Industry Invoice XML قابل للقراءة آلياً ومخزن داخل الملف كملف مرتبط. يصف كلا التمثيلين نفس الفاتورة. هذه الطبيعة المزدوجة هي الهدف الأساسي لعائلات التنسيقات التي تتطلبها الآن القوانين الأوروبية، مثل Factur-X في فرنسا وألمانيا، و ZUGFeRD في الأسواق الناطقة بالألمانية، و XRechnung لفواتير القطاع العام الألماني. تستعرض هذه المقالة كيفية تجميع PDFlibPas لمثل هذه الفاتورة الهجينة في Delphi، حيث تترك المعايير مجالاً للخطأ، ولماذا يحتاج أحد الملفات الشخصية في الكتالوج إلى مُنشئ XML منفصل تماماً

ما هي الفاتورة الهجينة في الواقع

تخدم الصفحة المرئية وملف XML المضمن قراء مختلفين. الموظف الذي يوافق على الدفع ينظر إلى الصفحة المصيرة. يستوعب نظام الحسابات الدائنة ملف XML، ويقرأ الإجماليات وتقسيم الضرائب كحقول مهيكلة، ويسجل القيد دون أن يكتب الإنسان أي شيء. يُحكم المحتوى الدلالي لملف XML هذا بواسطة EN 16931، وهو المعيار الأوروبي الذي يحدد نموذج بيانات الفاتورة: ما هي الحقول الموجودة، وماذا تعني، وأيها إلزامي. إن EN 16931 هو نموذج دلالي، وليس تنسيق ملف. تدرك كل من Factur-X و ZUGFeRD 2.x و XRechnung هذا النموذج كمستند UN/CEFACT Cross Industry Invoice، وهو بناء الجملة الذي يحمل حقول EN 16931 على الشبكة

لكي يكون المستند قابلاً للأرشفة وواصفاً لنفسه، فإن الحاوية هي PDF/A-3، والتي حددها ISO 19005-3. إن PDF/A-3 هو مستوى التوافق الذي يسمح بملفات مضمنة اعتباطية، وهو بالضبط ما يجب أن يكون عليه ملف XML الخاص بالفاتورة. يمنع PDF/A-2 تضمين الملفات التي ليست هي نفسها PDF/A، لذلك لا يمكن أن تكون فاتورة Factur-X بتنسيق PDF/A-2. وبالتالي فإن اختيار PDF/A-3 ليس تفضيلاً، بل هو مطلب ينبع مباشرة من الرغبة في تضمين بيانات غير PDF في مستند أرشيفي

لماذا العلاقة هي Alternative (بديل)

يُعد تضمين وحدات البايت هو الجزء السهل. يحدد ISO 32000 §7.11.4 دفق الملف المضمن، وهو الكائن الذي يحتفظ بملف XML الخام ومعلماته. الجزء الذي يجعل الملف ملفاً مرتبطاً صالحاً هو §14.13، والذي يضيف مفهوم الملف المرتبط والمفتاح /AFRelationship. يوضح هذا المفتاح كيفية ارتباط البيانات المضمنة بالمحتوى المرفق به، والقيمة التي يفرضها Factur-X هي Alternative

الخيار مهم لأن القيم الأخرى قد تؤكد شيئاً خاطئاً عن المستند. قد يعني Source أن ملف XML هو المادة التي تم إنشاء المحتوى المرئي منها، وهو أصل تُستمد منه الصفحة. قد يعني Supplement أن ملف XML يضيف معلومات تتجاوز ما تظهره الصفحة، كإضافة غير مضمنة في التصيير. لا هذا ولا ذاك يمثل ماهية فاتورة Factur-X. ملف XML والصفحة هما تعبيران متكافئان لفاتورة واحدة، ويحملان نفس المحتوى القانوني في شكلين. Alternative هي القيمة التي تقول ذلك بالضبط: تمثيل بديل مكافئ للمحتوى المرئي. سترفض أداة التحقق التي تقرأ أي علاقة أخرى على ملف Factur-X ذلك، وهي محقة في ذلك، لأن العلاقة هي ادعاء يمكن قراءته آلياً حول الغرض من المرفق

كتالوج الملفات الشخصية

يوجه نموذج الفاتورة الإلكترونية الذي يأتي مع PDFlibPas نفس مسار التوليد عبر ستة ملفات شخصية، محددة كمصفوفة من السجلات في InvoiceModel.pas. يحمل كل ملف شخصي القيم التي يحتاجها الكاتب: اسم العرض، واسم الملف المضمن، ومستوى التوافق، و /AFRelationship، والإصدار، ورمز البلد الاختياري، و URN الخاص بـ GuidelineID الذي يعلنه XML داخل سياق مستنده

الستة هم Factur-X EN16931، و Factur-X BASIC، و Factur-X EXTENDED لفرنسا، و XRechnung 3.0، و ZUGFeRD 1.0 COMFORT، و ZUGFeRD 2.0 BASIC. إن GuidelineID هو الحقل الذي يخبر المتلقي بدقة عن الملف الشخصي الذي يجب أن يتوقعه، والقيم محددة. يعلن Factur-X EN16931 عن urn:cen.eu:en16931:2017. يعلن XRechnung 3.0 عن urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. يعلن ZUGFeRD 2.0 BASIC عن urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. اسم الملف المضمن هو جزء من العقد أيضاً. تقوم ملفات Factur-X الشخصية بتضمين factur-x.xml، وتقوم XRechnung بتضمين xrechnung.xml، وتقوم ملفات ZUGFeRD الشخصية بتضمين ZUGFeRD-invoice.xml أو zugferd-invoice.xml. يقوم المتلقي بمسح أسماء المرفقات للعثور على الفاتورة، لذلك فإن اسم الملف ليس تجميلياً

تستحق إحدى التفاصيل في الكتالوج القراءة بعناية. تستخدم معظم الملفات الشخصية علاقة Alternative، ولكن إدخال XRechnung 3.0 في العينة يستخدم Source. يستجيب كلا التنسيقين لأدوات تحقق واصطلاحات مختلفة، وتقوم العينة بتعيين علاقة كل ملف شخصي من الكتالوج بدلاً من كتابة قيمة واحدة في التعليمات البرمجية، ولهذا السبب يوجد الحقل لكل ملف شخصي بدلاً من ثابت

فخ ZUGFeRD 1.0

من المغري أن نفترض أن كل ملف شخصي هو EN 16931 Cross Industry Invoice مع اختلافات طفيفة في عدد الحقول الاختيارية التي تملأها. ينطبق هذا على خمسة من أصل ستة. ولكنه لا ينطبق على ZUGFeRD 1.0 COMFORT، والسبب هيكلي وليس تجميلياً

تصدر الملفات الشخصية الحديثة فاتورة UN/CEFACT Cross Industry Invoice مع إصدار مساحة الاسم 100: ، والتي يكون عنصر الجذر الخاص بها هو rsm:CrossIndustryInvoice. يسبق ZUGFeRD 1.0 هذا المخطط. إنه مستند CrossIndustryDocument لعام 2014 مع إصدار مساحة الاسم 1p0: ، وعنصره الجذري هو rsm:CrossIndustryDocument. تختلف URNs لمساحة الاسم، ويختلف العنصر الجذري، وتختلف شجرة العناصر في جميع الأنحاء: يجمع المخطط 1p0: البيانات تحت ApplicableSupplyChainTradeAgreement و ApplicableSupplyChainTradeDelivery و ApplicableSupplyChainTradeSettlement، حيث يستخدم 100: كلاً من ApplicableHeaderTradeAgreement و ApplicableHeaderTradeDelivery و ApplicableHeaderTradeSettlement. التسمية متشابهة بما يكفي للتضليل ومختلفة بما يكفي للتعطيل

تصف كلمة COMFORT في اسم الملف الشخصي مدى ثراء البيانات، وهو ملف شخصي من درجة الأتمتة يتضمن عناصر سطر كاملة، وتفصيل الضرائب، وشروط الدفع، وليس أي مخطط يحمله. لذلك لا يمكنك أخذ مستند :100 وإعادة تسميته لـ ZUGFeRD 1.0. يعالج النموذج هذا الأمر بعلامة (flag) على كل سجل ملف شخصي ووظيفتي بناء منفصلتين، ويختار المناسب قبل إنشاء أي ملف XML

function BuildInvoiceXMLText(const AProfile: TeInvoiceProfile;
  const Data: TInvoiceData): string;
begin
  // XMLFamily = 1 means the legacy ZUGFeRD 1.0 :1p0 schema; every
  // other profile is the modern UN/CEFACT :100 Cross Industry Invoice.
  if AProfile.XMLFamily = 1 then
    Result := BuildZUGFeRD1Text(AProfile, Data)
  else
    Result := BuildCII100Text(AProfile, Data);
end;

الانقسام ليس مجرد دقة في التنفيذ. إن تغذية شجرة :100 إلى متلقي ZUGFeRD 1.0 ينتج مستنداً يفشل في التحقق من صحة المخطط عند العنصر الجذري، لذلك يجب بناء العائلتين بواسطة كود يعرف أيهما يكتب

تحديد مستوى PDF/A-3

يحتوي PDF/A-3 على ثلاثة مستويات توافق، ويقوم PDFlibPas بتحديدها من خلال SetPDFAMode. الوضع 5 هو PDF/A-3b، وهو المستوى الذي يضمن استنساخاً مرئياً موثوقاً. الوضع 6 هو PDF/A-3a، والذي يضيف البنية ذات العلامات (tagged-structure) ومتطلبات إمكانية الوصول الخاصة بالمستوى a. الوضع 7 هو PDF/A-3u، والذي يتطلب تعيين كل النص إلى Unicode. يؤدي تمكين الوضع أيضاً إلى تضمين نوايا الإخراج (output intent) sRGB المدمجة في المكتبة، وهي خاصية الألوان التي يتطلبها PDF/A بحيث يكون اللون المصير مُعرّفاً بدلاً من أن يعتمد على الجهاز

تعمل معظم مسارات الفواتير عند 3b، وهو ما يكفي لصفحة مرئية موثوقة بالإضافة إلى ملف XML المضمن. إذا كنت بحاجة إلى ملف شخصي ICC صريح بدلاً من الملف المدمج، فإن LoadOutputIntentProfile يقوم بتبديله بعد تعيين الوضع. يقوم النموذج بتحميل الملف الشخصي sRGB للمستودع بهذه الطريقة ويتراجع إلى النوايا المدمجة عندما لا يمكن الوصول إلى الملف، لذلك يكون output intent حاضراً دائماً

PDF := TPDFlib.Create;
try
  // Mode 5 = PDF/A-3b, 6 = PDF/A-3a, 7 = PDF/A-3u.
  if PDF.SetPDFAMode(5) <> 1 then
    raise Exception.Create('PDF/A-3 mode could not be enabled');

  // Optional: swap the built-in sRGB intent for an explicit ICC profile.
  if PDF.LoadOutputIntentProfile(ICCFile, 'DeviceRGB') <> 1 then
    { fall back to the built-in sRGB intent that SetPDFAMode embedded };
finally
  // ... continue building the document
end;

بناء الفاتورة الهجينة

مع تكوين الحاوية، يصبح الباقي ثلاث خطوات بالترتيب: تعيين وضع PDF/A-3، ورسم الصفحة القابلة للقراءة من قبل الإنسان، ثم إرفاق ملف XML كملف مرتبط. الصفحة المرئية هي محتوى عادي. القيد الوحيد الذي يستحق التذكر هو أن PDF/A يمنع الخطوط الـ 14 القياسية غير المضمنة، لذلك يجب أن تضمن الصفحة وجه خط حقيقي بدلاً من الإشارة إلى خط مدمج

المرفق هو استدعاء واحد. تأخذ AddFacturXAssociatedFileFromString وحدات بايت XML الخام بترميز UTF-8 بالإضافة إلى البيانات الوصفية للملف الشخصي، وتكتب دفق الملف المضمن، وتسجله في مصفوفة /AF للكتالوج التي يتطلبها PDF/A-3، وتطبق /AFRelationship، وتنشئ البيانات الوصفية للفاتورة الإلكترونية XMP التي تحدد المستند على أنه Factur-X أو ZUGFeRD أو XRechnung. كما يتحقق من أن مُعرّف المبدأ التوجيهي لـ XML يطابق مستوى التوافق الذي طلبته، لذلك يتم التقاط عدم التطابق بين ملف XML الذي قمت ببنائه والملف الشخصي الذي قمت بتسميته بدلاً من شحنه بصمت

// 1. PDF/A-3 mode and output intent are already set.
// 2. Draw the visible page (embeds a real TrueType font).
DrawInvoicePage(PDF, AProfile, Data);

// 3. Build the profile-correct XML and attach it as an
//    associated file with /AFRelationship = Alternative.
InvoiceXML := BuildInvoiceXML(AProfile, Data);   // AnsiString of UTF-8 bytes
FileID := PDF.AddFacturXAssociatedFileFromString(
  InvoiceXML,
  AProfile.ConformanceLevel,   // e.g. 'EN16931'
  AProfile.FileName,           // 'factur-x.xml'
  AProfile.Description,
  AProfile.Relationship,       // 'Alternative'
  AProfile.Version,            // '1.0'
  AProfile.CountryCode);       // '' or 'DE' or 'FR'
if FileID <= 0 then
  raise Exception.Create('Invoice XML could not be attached');

PDF.SaveToFile(TargetFile);

أحد التفاصيل الدقيقة في مسار البيانات هو التشفير. يعلن ملف XML المضمن عن encoding="UTF-8"، وتأخذ الطريقة وحدات البايت الخاصة بها كـ AnsiString، لذلك يجب أن يصل اسم البائع أو المشتري غير التابع لـ ASCII إلى الاستدعاء كثمانيات UTF-8 خام. قد يؤدي الإرسال العادي عبر صفحة تعليمات ANSI البرمجية للنظام إلى إتلاف تلك الأحرف وإنتاج فاتورة بهدوء لا يتطابق ملف XML الخاص بها مع إعلانها. يقوم النموذج بالتشفير إلى UTF-8 بشكل صريح قبل تسليم وحدات البايت، وهي الطريقة الآمنة لتغذية أي واجهة برمجة تطبيقات PDF موجهة بالبايت من سلسلة (string) Unicode

لإرفاق ملف XML ليس ملفاً شخصياً معترفاً به للفاتورة الإلكترونية، فإن AddPDFA3AssociatedFileFromString هو النظير العام. إنه يأخذ اسم ملف، ونوع MIME، ووصفاً، وعلاقة، ووحدات بايت، ويكتب ملفاً عادياً مرتبطاً بـ PDF/A-3 بدون أي بيانات وصفية خاصة بالفاتورة أو فحوصات مبادئ توجيهية. استخدمه للبيانات التكميلية؛ استخدم طريقة Factur-X للفواتير، بحيث تتم كتابة البيانات الوصفية للملف الشخصي وتطابق المبدأ التوجيهي لك

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