مقاله فنی

اعتبارسنجی فاکتورهای الکترونیکی: veraPDF و Mustang در دلفی

یک فاکتور Factur-X یا ZUGFeRD در واقع دو سند است که یک نام فایل مشترک به تن کرده‌اند. سند بیرونی یک کانتینر (container) از نوع PDF/A-3 است که یک ابزار خواندنِ بایگانی باید آن را برای ده سال آینده بپذیرد. سند درونی یک فاکتور XML است که سیستم حسابداری خریدار باید آن را بر اساس EN 16931 تجزیه (parse) کند. اشتباهی که باعث ارسال فاکتورهای خراب به مرحله تولید (production) می‌شود، این باور است که اگر اولی را درست انجام دهید، دومی را به صورت رایگان به دست آورده‌اید. اما این‌طور نیست. یک فایل می‌تواند یک PDF/A-3 بی‌نقص باشد و با این حال حاوی یک XML باشد که هیچ اداره مالیاتی‌ای آن را نپذیرد، و از طرف دیگر می‌تواند شامل یک XML کاملاً منطبق با EN 16931 در داخل یک کانتینر باشد که در اعتبارسنجیِ بایگانی (archival validation) مردود می‌شود. این دو لایه توسط دو ابزار متفاوت که هیچ چیزی درباره یکدیگر نمی‌دانند اعتبارسنجی می‌شوند، و یک پایپ‌لاینِ (pipeline) واقعی باید هر دوی آن‌ها را راضی کند

دو اعتبارسنج (validator)، دو سوال متفاوت

نرم‌افزار veraPDF پیاده‌سازی مرجع (reference implementation) برای PDF/A است. آن را به سمت یک فاکتور نشانه‌گیری کنید و او به یک سوال پاسخ می‌دهد: آیا این یک فایلِ PDF/A-3 منطبق (conformant) است؟ او مواردی را بررسی می‌کند که برای ISO 19005-3 اهمیت دارند. آیا همه فونت‌ها تعبیه (embedded) شده‌اند؟ آیا یک OutputIntent وجود دارد؟ آیا متادیتاهای XMP بخش و سطح تطابق درستی را اعلام می‌کنند؟ برای یک فاکتور الکترونیکی، او همچنین لوله‌کشیِ فایل‌های مرتبط (associated-file plumbing) را که در PDF/A-3 الزامی است چک می‌کند، زیرا XML به عنوان یک فایل تعبیه‌شده سوار بر /AFRelationship است و مدخلی در آرایه /AF کاتالوگِ سند دارد. veraPDF هیچ چیزی درباره اینکه آیا جمع کلِ فاکتور درست محاسبه شده است یا خیر نمی‌گوید، زیرا این موضوع در حیطه وظایف او نیست

نرم‌افزار Mustang یک اعتبارسنج متن‌باز از پروژه Mustangproject است. او سوال متعامد (orthogonal) دیگری می‌پرسد: آیا XML تعبیه‌شده یک فاکتور معتبر است؟ او XML را در برابر شمایِ (schema) پروفایلِ اعلام‌شده اجرا می‌کند و سپس قوانین تجاریِ EN 16931 و مجموعه‌قوانینِ خاص‌کشور را که روی آن لایه‌بندی شده‌اند اعمال می‌نماید، از جمله CIUS در XRechnung. او بررسی می‌کند که آیا در مواقعی که جمع کل ایجاب می‌کند، شناسه VAT فروشنده وجود دارد یا خیر؛ آیا مبالغ تخفیف (allowance) و هزینه (charge) با جمع کل سند هم‌خوانی دارند، و آیا URN پروفایل در XML با آنچه فایل ادعا می‌کند مطابقت دارد یا خیر. Mustang اهمیتی نمی‌دهد که آیا PDFِ احاطه‌کننده، فونت‌های خود را تعبیه کرده است یا نه، زیرا این وظیفه veraPDF است

هیچ‌یک از این دو ابزار فرامجموعه‌ای (superset) از دیگری نیست. veraPDF یک کانتینر از لحاظ ساختاری کامل را می‌پذیرد که به دور یک XML بی‌معنی پیچیده شده است. Mustang یک XML بی‌نقص را که در داخل کانتینری بدون OutputIntent بسته‌بندی شده است قبول می‌کند. هر کدام دقیقاً کلاسی از نقص‌ها را می‌گیرند که دیگری نسبت به آن‌ها نابینا است، و این تنها دلیلی است که یک سیستم اعتبارسنجی جدی هر دوی آن‌ها را اجرا می‌کند و یک فایل را تنها در صورتی قابل‌ارسال در نظر می‌گیرد که هر دو ابزار با آن موافق باشند

ماتریس اعتبارسنجی

برای اثبات اینکه کتابخانه فایل‌هایی تولید می‌کند که از هر دو دروازه به سلامت عبور می‌کنند، سیستمِ آزمون یک ماتریس می‌سازد. شش پروفایل فاکتور، طیفی را که یک پایپ‌لاین اروپایی در عمل با آن روبرو می‌شود پوشش می‌دهند: Factur-X EN 16931، و Factur-X BASIC، و واریانتِ B2B کشور فرانسه یعنی Factur-X EXTENDED، و XRechnung 3.0، و ZUGFeRD 1.0 COMFORT، و ZUGFeRD 2.0 BASIC. هر پروفایل در برابر دو سطحِ زیرتطابق (sub-conformance levels) از PDF/A یعنی 3b و 3u تولید می‌شود، زیرا الزامات سطوح B و U بر روی مپینگِ (mapping) یونیکد با هم تفاوت دارند و فایلی که در یکی موفق شود ممکن است در دیگری مردود گردد. شش پروفایل ضرب در دو سطح می‌شود دوازده فایل، که تک‌تکِ آن‌ها به صورت هدلس (headless) و دقیقاً از طریق همان مسیر کدی که نمونه کاربری (GUI sample) ارائه می‌دهد ساخته شده‌اند، در نتیجه آرتیفکت‌های تحتِ آزمایش برای این آزمون به صورت دستی تنظیم (hand-tuned) نشده‌اند

تولیدکننده (generator) هر دوازده مورد را می‌نویسد و یک اسکریپت هر یک از آن‌ها را به هر دو اعتبارسنج می‌دهد. در اولین اجرای کامل، veraPDF هر دوازده مورد را قبول کرد. لوله‌کشیِ کانتینر در تمام موارد صحیح بود: فایل‌های مرتبط ثبت شده بودند، تطابق XMP اعلام شده بود، OutputIntentها در جای خود قرار داشتند. اما Mustang فقط هشت مورد را قبول کرد. چهار فاکتور از لحاظ ساختاری فایل‌های PDF/A-3 معتبری بودند که حامل XMLای بودند که اعتبارسنجِ قوانینِ تجاری آن‌ها را رد می‌کرد، و این دقیقاً همان تفکیکی است که رویکردِ دو-ابزاری برای نمایان کردن آن وجود دارد. اگر سیستم تنها به veraPDF اعتماد کرده بود، آن چهار مورد به نظر تکمیل‌شده می‌رسیدند

دو اصلاحی که شکاف را پر کردند

چهار مردودیِ Mustang ناشی از دو علت مجزا بودند، و راه‌حلِ هر کدام جزئیاتی است که ارزش دانستن را دارد پیش از آنکه شما خودتان این پروفایل‌ها را تولید کنید

مورد اول مربوط به پروفایلِ Factur-X EXTENDED France B2B بود. تولیدکننده اصلی یک لیبل داخلی را به عنوان سطح تطابق و یک URN داخلی را به عنوان گایدلاین (guideline) فرستاده بود، و Mustang فایل را با خطای invalid-conformance-value و به دنبال آن خطای unsupported-profile-type رد کرد. دلیل این است که فیلد fx:ConformanceLevel در XMP یک جایگاهِ متنیِ آزاد (free-text slot) برای نام‌گذاریِ پروفایلِ دلخواه شما نیست. Factur-X دقیقاً پنج مقدار استاندارد برای آن تعریف می‌کند: MINIMUM، و BASIC WL، و BASIC، و EN 16931، و EXTENDED. یک فاکتور B2B خاص کشور فرانسه تا جایی که به متادیتاهای XMP مربوط می‌شود همچنان یک سند با پروفایل EXTENDED است. کاراکتر فرانسویِ فاکتور با ابداعِ ششمین مقدار برای تطابق بیان نمی‌شود. بلکه از طریق کد کشور، FR، و توسط شناسه گایدلاین در داخل XML بیان می‌شود، که باید حامل پیشوندِ urn:cen.eu:en16931:2017#conformant# باشد تا CIUSای منطبق بر EN 16931 را نشان دهد. ارسال مقدار استاندارد EXTENDED با FR به عنوان کد کشور و URN صحیحِ گایدلاین، فایل را منطبق (conformant) ساخت

در APIِ کتابخانه، این به معنای فراخوانی AddFacturXAssociatedFileFromString با هم‌تراز کردن تطابق، کشور و گایدلاین است. آرگومانِ سطح تطابق حامل توکن استاندارد است، آرگومان کد کشور FR را حمل می‌کند، و URN گایدلاین در بایت‌های XMLای قرار دارد که شما به عنوان پارامتر پاس می‌دهید

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(5);            // PDF/A-3b
  PDF.NewDocument;
  // ... رسم صفحه فاکتور قابل‌خواندن برای انسان ...
  // فایل ExtendedXML حامل یک URN گایدلاینِ EN 16931 به شکل زیر است
  //   urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
  FileID := PDF.AddFacturXAssociatedFileFromString(
    ExtendedXML,
    'EXTENDED',          // مقدار استاندارد fx:ConformanceLevel، نه یک لیبل داخلی
    'factur-x.xml',
    'Factur-X EXTENDED invoice',
    'Alternative',       // /AFRelationship
    '1.0',
    'FR');               // فاکتور B2B فرانسه با کد کشور مشخص می‌شود، نه با تطابق
  if FileID = 0 then
    raise Exception.Create('پیوست Factur-X رد شد');
  PDF.SaveToFile('02_Factur-X-EXTENDED-FR_PDFA-3b.pdf');
end;

دومین علت مربوط به پروفایل ZUGFeRD 1.0 COMFORT بود، و هیچ ارتباطی به متادیتا نداشت. ZUGFeRD 1.0 در برابر :1p0 XSD اعتبارسنجی می‌شود، که در مورد کاردینالیتی (cardinality) بسیار سخت‌گیرانه‌تر از آن چیزی است که خلاصه‌های متنی نشان می‌دهند. شمایِ XSD الزام می‌کند که سربرگِ جمع‌بندی تسویه (header settlement summation)، ram:SpecifiedTradeSettlementMonetarySummation، شامل ram:ChargeTotalAmount و ram:AllowanceTotalAmount هر کدام دقیقاً یک‌بار باشد. XMLِ تولیدشده هر دو را از قلم انداخته بود، بنابراین Mustang گزارش داد که این عناصر باید دقیقاً یک‌بار رخ دهند. زمانی که شما می‌گوید minOccurs یک است، این‌ها اختیاری (optional) نیستند. ارسال هر دو عنصر در همان ترتیبِ توالیِ XSD، بلافاصله پس از ram:LineTotalAmount، با مقداری برابر 0.00 زمانی که هیچ هزینه یا تخفیفی وجود ندارد، شما را راضی کرد. یک صفر به معنای وجود عنصر است؛ غیبتِ یک عنصر، نقض شمایِ XSD محسوب می‌شود. با قرار گرفتن این دو اصلاحیه در جای خود، ماتریس در Mustang نیز به رکورد دوازده از دوازده رسید در حالی که در veraPDF نیز همچنان دوازده از دوازده بود

فیلدهای XRechnung که نامعتبر را به معتبر تبدیل می‌کنند

پروفایل XRechnung شایسته یک یادداشت اختصاصی است زیرا CIUS آلمانیِ آن قوانین تجاری‌ای را اضافه می‌کند که در مجموعه پایه EN 16931 وجود ندارند، و در صورت رعایت نشدن به گونه‌ای خطا می‌دهند که در نگاه اول به نظر می‌رسد هیچ مشکلی در سند وجود ندارد. دو مورد از آن‌ها به آدرس‌های الکترونیکی مربوط می‌شوند. فیلد BT-34 آدرس الکترونیکی فروشنده و BT-49 آدرس الکترونیکی خریدار است، نقاط پایانیِ مسیریابی‌ای (routing endpoints) که پورتال بخش عمومیِ آلمان برای تحویل و تایید فاکتور از آن‌ها استفاده می‌کند. مدل پایه EN 16931 آن‌ها را اختیاری می‌داند. اما XRechnung این‌طور نیست. هر یک از آن‌ها را از قلم بیندازید و فاکتور از نظر ساختار درست است، از نظر شمایِ XSD معتبر است، اما مردود (rejected) می‌شود

سومین مورد، قانون BR-DE-6 است که الزام می‌کند شماره تلفن تماس فروشنده باید حضور داشته باشد. این از آن دست فیلدهایی است که یک توسعه‌دهنده به راحتی حذف می‌کند زیرا بیشتر شبیه اطلاعات نمایشی به نظر می‌رسد تا داده‌های اساسی، و عدم حضور آن باعث یک خطای اعتبارسنجی می‌شود که به جای اشاره به چیزی که آشکارا از دست رفته، به گروهِ تماس فروشنده اشاره می‌کند. تامینِ BT-34، BT-49، و شماره تلفن فروشنده همان چیزی است که یک فایل XRechnung را تحتِ بررسیِ Mustang از نامعتبر به معتبر منتقل می‌کند، و هیچ‌کدام از این‌ها هیچ‌چیزی از آنچه veraPDF می‌بیند را تغییر نمی‌دهند، زیرا هر سه مورد در درونِ XML زندگی می‌کنند

اتصال خروجی کتابخانه به یک اعتبارسنج

نکته معماری‌ای که در پشت این سیستمِ آزمون قرار دارد قابل‌تعمیم به هر سیستم تجاری دیگری است. کتابخانه PDF یک کانتینر منطبق (conformant container) می‌نویسد و XML را در آن تعبیه می‌کند. او تلاش نمی‌کند تا مرجع قوانین تجاری EN 16931 باشد، و نباید هم چنین کاری کند. تابع ValidateFacturXInvoice در کتابخانه، سازگاری کانتینر را بررسی می‌کند، اینکه آرایه /AF در کاتالوگ، درختِ نام‌های فایل‌های تعبیه‌شده، فیلدِ DocumentFileName در XMP، پروفایل، گایدلاین، و /AFRelationship همگی با هم توافق دارند، اما این تابع کدهای مالیاتی را اعتبارسنجی نکرده یا مبالغ را با هم تطبیق (reconcile) نمی‌دهد. تقسیم کار درست این است که سیستم تجاری 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
    Writeln('Profile:   ', Profile);
    Writeln('Guideline: ', Guideline);
    // فایل خام XML را به یک اعتبارسنج اختصاصی EN 16931 / Mustang بسپارید.
    Result := PDF.ExtractFacturXXMLToString;
  end;
end;

تابع ExtractFacturXXMLToString بایت‌های خامِ XML را به عنوان یک AnsiString برمی‌گرداند، که آماده نوشتن در یک فایل یا ارسال به یک فرآیندِ (process) اعتبارسنج است. در سیستم آزمون، این هدف همان Mustang است که از طریق فایلِ command-line jarِ خود فراخوانی می‌شود، در حالی که veraPDF در همان گذرِ تکرار بر روی همان فایل اجرا می‌گردد. سیم‌کشیِ کار بسیار کوچک است: یک تولیدکننده کنسولی به نام EInvoiceValidation.dpr با استفاده از مدلِ مشترک فاکتور از نمونه، دوازده فایل را می‌نویسد، و یک اسکریپت به نام run-validation.ps1 هر دو اعتبارسنج را بر روی دایرکتوریِ خروجی می‌راند و یک جدول قبولی (pass) و مردودی (fail) چاپ می‌کند. همین شکلِ دو مرحله‌ایِ ساده (تولید با کتابخانه و تایید با اعتبارسنج‌های خارجی) دقیقاً همان چیزی است که یک وظیفه یکپارچه‌سازی مداوم (continuous-integration job) باید در هر تغییری در تولیدِ فاکتور اجرا کند، زیرا تنها راه برای دانستن اینکه یک فایل هر دو لایه را راضی می‌کند، پرسیدن از هر دو ابزار است

اگر پایپ‌لاین شما همچنین باید کانتینر را پیش از امضا (signing) گواهی (certify) کند، سمتِ preflight این کار در مقاله بررسیِ preflight در PDF/A و PDF/UA در دلفی پوشش داده شده است، و جریان گسترده‌ترِ تایید-و-سپس-امضا در مقاله میزکار تطابق و امضا توصیف گردیده است. هر دوی این‌ها بر پایه همان مسیر تولیدی بنا شده‌اند که به عنوان بخشی از کتابخانه PDF دلفی برای دلفی (Delphi) و سی‌پلاس‌پلاس‌بیلدر (C++Builder)، در کنار APIهای PDF/A، فایل‌های مرتبط و متادیتاهایی که در اینجا استفاده شد، عرضه می‌شود