บทความทางเทคนิค

ZUGFeRD และ Factur-X E-Invoices ใน Delphi ด้วย HotPDF

HotPDF ทำหน้าที่สร้าง, ตรวจจับ และตรวจสอบความถูกต้องของใบแจ้งหนี้อิเล็กทรอนิกส์ ZUGFeRD และ Factur-X จาก Delphi และ C++Builder ฟังก์ชัน AddFacturXAssociatedFile จะฝังไฟล์ XML ใบแจ้งหนี้ตามมาตรฐาน EN 16931 ลงในคอนเทนเนอร์ PDF/A-3, ฟังก์ชัน DetectFacturXInvoice จะตรวจจับใบแจ้งหนี้แบบไฮบริดที่รับเข้ามา และฟังก์ชัน HPDFValidateEInvoice จะรันกฎทางธุรกิจ EN 16931 Schematron อย่างเป็นทางการกับไฟล์ XML ที่ฝังอยู่ บทความนี้จะนำเสนอทั้ง 3 ขั้นตอนและชี้แจงอย่างตรงไปตรงมาเกี่ยวกับจุดที่เอนจินตรวจสอบความถูกต้องหยุดทำงาน

แรงผลักดันเบื้องหลังชุดฟีเจอร์นี้เป็นเรื่องของกฎระเบียบข้อบังคับ ไม่ใช่เรื่องของเทคโนโลยี ประเทศเยอรมนีและฝรั่งเศสกำลังเริ่มบังคับใช้ใบแจ้งหนี้อิเล็กทรอนิกส์แบบมีโครงสร้างสำหรับการทำธุรกรรม B2B ภายในประเทศ และข้อบังคับของทั้งสองประเทศก็มาบรรจบกันที่โมเดลไฮบริดแบบเดียวกัน: นั่นคือใบแจ้งหนี้ PDF ที่ระบบ ERP ของคุณสร้างขึ้นอยู่แล้ว จะต้องบรรจุไฟล์ XML แบบที่เครื่องสามารถอ่านได้ควบคู่ไปด้วย แอปพลิเคชันบัญชีหรือระบบ ERP บน Delphi ที่ปัจจุบันส่งออกเฉพาะใบแจ้งหนี้ PDF ธรรมดานั้น มีกำหนดเวลาที่เข้มงวดในการเริ่มส่งออกไฟล์ Factur-X หรือ ZUGFeRD และความแตกต่างระหว่างสองชื่อนี้ก็น้อยกว่าที่การตลาดโฆษณาไว้ — ZUGFeRD และ Factur-X คือมาตรฐานร่วมระหว่างฝรั่งเศสและเยอรมนีตัวเดียวกันที่เผยแพร่ภายใต้ชื่อสองชื่อ

อะไรที่ทำให้ PDF กลายเป็นใบแจ้งหนี้ Factur-X ที่ถูกต้อง?

ใบแจ้งหนี้ Factur-X ที่ถูกต้องคือไฟล์ PDF/A-3 (ISO 19005-3) ที่ฝังไฟล์ XML ใบแจ้งหนี้แบบมีโครงสร้างไว้เป็นไฟล์ร่วม (associated file) เพียงไฟล์เดียว เชื่อมโยงมันจากแคตตาล็อกเอกสาร และประกาศไว้ในข้อมูลเมตาส่วนขยาย XMP ซึ่งข้อมูลทั้ง 3 เลเยอร์จะต้องสอดคล้องกัน ประการแรก ตัวคอนเทนเนอร์ต้องสอดคล้องกับมาตรฐาน PDF/A-3 ซึ่งเป็นโปรไฟล์สำหรับการเก็บถาวรที่อนุญาตให้ฝังไฟล์ประเภทใดก็ได้ ต่างจาก PDF/A-1 และ PDF/A-2 ประการที่สอง ไฟล์ XML ใบแจ้งหนี้ที่ชื่อ factur-x.xml สำหรับโปรไฟล์มาตรฐาน หรือชื่อ xrechnung.xml สำหรับเวอร์ชัน XRechnung ของเยอรมนี จะต้องลงทะเบียนในอาร์เรย์ /AF ของแคตตาล็อก และทรี /Names /EmbeddedFiles พร้อมมีข้อมูล AFRelationship ระบุว่าไฟล์แนบคืออะไร: ซึ่งคำว่า Alternative หมายถึงไฟล์ XML เป็นตัวแทนที่เทียบเท่าของใบแจ้งหนี้ที่มองเห็นได้ ซึ่งเป็นความหมายที่โปรไฟล์ส่วนใหญ่กำหนด ประการที่สาม Factur-X XMP extension schema ต้องระบุชื่อไฟล์ที่ฝัง, ระดับความสอดคล้อง (conformance level) และประเภทเอกสาร เพื่อให้ระบบรับข้อมูลสามารถจัดประเภทใบแจ้งหนี้ได้โดยไม่ต้องแกะข้อมูล XML เลย

ตัวข้อมูล XML เองจะเป็นไปตามโมเดลความหมาย EN 16931 ซึ่งเป็นมาตรฐานยุโรปที่กำหนดแกนหลักของใบแจ้งหนี้: ผู้ซื้อ, ผู้ขาย, รายการแถวข้อมูล, รายละเอียดภาษี, ยอดเงินที่ต้องชำระ โดยแต่ละส่วนจะมีหมายเลขคำศัพท์ทางธุรกิจ (เช่น BT-1 สำหรับเลขใบแจ้งหนี้, BT-5 สำหรับสกุลเงิน เป็นต้น) ดังนั้นการตรวจสอบมาตรฐาน EN 16931 จึงเกิดขึ้นบนสองเลเยอร์ที่ต่างกัน การตรวจสอบโครงสร้าง XML (XML schema validation) จะตรวจว่าเอกสารเป็นโครงสร้าง CII ที่ถูกต้องพร้อมมีชื่อองค์ประกอบที่ถูกต้อง ส่วนกฎทางธุรกิจ Schematron จะตรวจว่าเนื้อหามีความสอดคล้องกัน — เช่น กฎ BR-CO-15 ระบุว่ายอดรวมใบแจ้งหนี้ต้องเท่ากับผลรวมของยอดสุทธิของแต่ละบรรทัดบวกกับภาษีมูลค่าเพิ่มจริง และคำศัพท์ทางธุรกิจที่จำเป็นต้องมีอยู่ครบถ้วนสำหรับโปรไฟล์ที่ประกาศ ไฟล์เอกสารอาจผ่านการตรวจสอบโครงสร้าง XML แต่ก็ยังเป็นใบแจ้งหนี้ที่ไม่ถูกต้องได้ ซึ่งนั่นคือสาเหตุที่มีเลเยอร์กฎทางธุรกิจขึ้นมา

ทำไม ZUGFeRD 2.5 จึงไม่ต้องใช้คอนเทนเนอร์ใหม่

ZUGFeRD 2.5 ไม่มีการเปลี่ยนแปลงใดๆ ในระดับคอนเทนเนอร์ PDF: ทั้ง URN แนวทางปฏิบัติ, โครงสร้าง XMP และโทเค็นเวอร์ชันจะเหมือนกันกับ ZUGFeRD 2.3 / Factur-X 1.0 ทุกประการ เรื่องนี้มักจะสร้างความประหลาดใจให้ผู้พัฒนาส่วนใหญ่ ดังนั้นจึงจำเป็นต้องอ้างอิงข้อมูลจากแหล่งข้อมูลอย่างเป็นทางการ ข้อกำหนดของ Factur-X 1.09 ระบุว่าเลขเวอร์ชันใน URI ของ PDF/A extension schema จะไม่เกี่ยวข้องกับเลขเวอร์ชันของข้อกำหนดข้อมูล XML และเลขเวอร์ชันของอินสแตนซ์ใบแจ้งหนี้ Factur-X จะยังคงเป็น 1.0 เสมอ — ทั้งใน fx:Version ของ XMP และใน BT-24 of factur-x.xml คลังตัวอย่าง ZUGFeRD 2.5 อย่างเป็นทางการยืนยันข้อนี้: ตัวอย่างทุกตัวจะฝัง factur-x.xml ภายใต้แนวทาง URN urn:factur-x.eu:1p0 ร่วมกับค่า fx:Version เป็น 1.0 สิ่งที่รุ่น 2.5 เพิ่มเข้ามาจริงๆ นั้นจะอยู่ภายในโมเดลข้อมูล XML — เช่น องค์ประกอบใหม่ๆ อย่างบรรทัดใบแจ้งหนี้ย่อยและตัวระบุชุดข้อมูล — ซึ่งเป็นเลเยอร์ทางธุรกิจที่แอปพลิเคชันของคุณสร้างขึ้น ไม่ใช่เลเยอร์คอนเทนเนอร์ที่ไลบรารี PDF จัดการ

HotPDF นำผลลัพธ์นี้มาใช้โดยตรง ฟังก์ชัน AddFacturXAssociatedFile จะรับพารามิเตอร์ Version และปรับรูปแบบผ่าน HPDFFacturXNormalizeVersion ก่อนเขียน XMP ดังนั้นผู้เรียกใช้งานที่ส่งชื่อแฝง 2p5 ก็จะยังคงสร้างโทเค็นที่สอดคล้องกับข้อกำหนดออกไป แทนที่จะสร้างเวอร์ชัน XMP 2p5 ขึ้นมาเองซึ่งจะไม่มีโปรแกรมตรวจสอบใดๆ ยอมรับ ส่วนระบบช่วยตรวจจับจะถือว่า 2p5 มีค่าเทียบเท่าเมื่อทำการอ่าน เพื่อให้ระบบไปกลับยังคงสมมาตรกัน หากไลบรารีหรือเครื่องมือใดบอกคุณว่ามันเขียนคอนเทนเนอร์ ZUGFeRD 2.5 แบบพิเศษ ตัวข้อกำหนดของมาตรฐานเองยืนยันว่าไม่มีสิ่งนั้นอยู่จริง

การฝัง XML ใบแจ้งหนี้จาก Delphi

var
  Pdf: THotPDF;
  InvoiceXML, UBLXML: TBytes;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-2026-0042.pdf';
    Pdf.PDFACompliance := '3B';            // PDF/A-3b container
    Pdf.BeginDoc;
    // embed the EN 16931 CII invoice XML as factur-x.xml
    InvoiceXML := BuildInvoiceXML;         // produced by your business layer
    Pdf.AddFacturXAssociatedFile(InvoiceXML, 'EN 16931');
    // optional: attach a UBL view as a supplementary representation
    UBLXML := BuildUBLXML;
    Pdf.AddUBLSupplementaryFile(UBLXML);   // factur-xubl.xml, Alternative
    // ... draw the visible invoice page here ...
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

ค่าเริ่มต้นจะเป็นไปตามข้อกำหนด: ชื่อไฟล์คือ factur-x.xml, ความสัมพันธ์ Alternative, เวอร์ชัน 1.0 ฟังก์ชัน AddUBLSupplementaryFile จะทำงานตามส่วนที่ 6.4 ของข้อกำหนด Factur-X 1.09 ซึ่งอนุญาตให้นำการแสดงรูปแบบ UBL ของใบแจ้งหนี้เดียวกันแนบไปด้วยเป็นไฟล์แนบที่สองชื่อ factur-xubl.xml พร้อม MIME type application/xml และระบุความสัมพันธ์ Alternative ลำดับขั้นตอนมีความสำคัญที่นี่ และ HotPDF จะบังคับใช้เงื่อนไขตามที่ข้อกำหนดระบุไว้: นั่นคือไฟล์ UBL จะเป็นมุมมองเสริมเท่านั้น ไม่มีทางเป็นใบแจ้งหนี้หลัก ดังนั้นมันต้องถูกเพิ่มหลังจากฟังก์ชัน AddFacturXAssociatedFile ได้ลงทะเบียนตัวหลัก CII เรียบร้อยแล้ว ข้อควรทราบคือ หน้าเอกสารที่วาดขึ้นและไฟล์ XML ที่ฝังอยู่จะต้องระบุข้อมูลใบแจ้งหนี้ตัวเดียวกัน — ไฟล์ PDF ที่แสดงยอดรวมยอดหนึ่งแต่เข้ารหัสไว้อีกยอดหนึ่งนั้นคือโหมดความล้มเหลวที่ระบบใบแจ้งหนี้แบบไฮบริดพยายามป้องกัน ฝั่ง PDF/A-3 ของกระบวนการนี้ ซึ่งรวมถึง OutputIntent และการฝังฟอนต์ ได้รับการครอบคลุมรายละเอียดไว้แล้วในบทความคู่กันเกี่ยวกับการตรวจสอบความถูกต้องของ PDF/A, PDF/X และ PDF/UA ใน Delphi

จะตรวจจับใบแจ้งหนี้ Factur-X ใน PDF ที่รับเข้ามาได้อย่างไร?

var
  Reader: THotPDF;
  Info: THPDFFacturXInvoiceInfo;
begin
  Reader := THotPDF.Create(nil);
  try
    if Reader.LoadFromFile('incoming-invoice.pdf') <= 0 then
      raise Exception.Create('No pages loaded.');
    if Reader.DetectFacturXInvoice(Info) then
    begin
      Writeln('Invoice XML:    ', string(Info.FileName));
      Writeln('Conformance:    ', string(Info.ConformanceLevel));
      Writeln('Guideline URN:  ', string(Info.GuidelineID));
      Writeln('Relationship:   ', string(Info.AFRelationship));
      if Info.UBLFileName <> '' then
        Writeln('UBL view:       ', string(Info.UBLFileName));
    end
    else
      Writeln('Not a Factur-X / ZUGFeRD hybrid invoice.');
  finally
    Reader.Free;
  end;
end;

การตรวจจับจะอ่านสัญญาณเดียวกับที่โปรแกรมตรวจสอบมาตรฐานทั่วไปอ่าน — นั่นคือ XMP extension schema และทะเบียนไฟล์ร่วม (associated-file registry) — แทนที่จะเป็นการคาดเดาจากชื่อไฟล์เพียงอย่างเดียว วัตถุเอกสารที่โหลดขึ้นมาตัวเดียวกันนี้จะเปิดเผยข้อมูลเมตาของ XMP และพจนานุกรมข้อมูลสำหรับการตรวจสอบหรือแก้ไข ซึ่งเป็นกระบวนการทำงานที่อธิบายไว้ในบทความเกี่ยวกับการแก้ไขข้อมูลเมตาในเอกสาร PDF ที่โหลดขึ้นมา

กฎทางธุรกิจหรือโครงสร้าง: การตรวจสอบมาตรฐาน EN 16931 หมายความว่าอย่างไร?

ฟังก์ชัน HPDFValidateEInvoice ซึ่งเป็นฟังก์ชันอิสระในยูนิต HPDFEInvoiceValidator จะรันเลเยอร์ตรวจสอบทั้งสองตามลำดับ: ขั้นแรกคือการตรวจสอบโครงสร้างคอนเทนเนอร์ผ่านทาง ValidateFacturXInvoice จากนั้นจึงตรวจสอบกฎทางธุรกิจ EN 16931 Schematron กับไฟล์ XML ใบแจ้งหนี้ที่แยกออกมา HotPDF จะจัดส่งไฟล์กฎ Factur-X 1.09 อย่างเป็นทางการทั้ง 5 ไฟล์ — MINIMUM, BASIC WL, BASIC, EN 16931 และ EXTENDED — ไว้ที่ตำแหน่ง Lib/resources/Schematron/ และฟังก์ชัน HPDFSchematronFileForProfile จะเลือกไฟล์ที่ตรงกับระดับความสอดคล้องที่ตรวจพบ ดังนั้นใบแจ้งหนี้ตามมาตรฐาน EN 16931 จะถูกตรวจสอบเทียบกับไฟล์ Factur-X_1.09_EN16931.sch โดยอัตโนมัติ ฟังก์ชันจะส่งกลับค่า True ก็ต่อเมื่อโครงสร้างคอนเทนเนอร์ถูกต้องและไม่มีกฎทางธุรกิจใดๆ ถูกละเมิด

uses HPDFEInvoiceValidator;

var
  Reader: THotPDF;
  Report: THPDFEInvoiceValidationReport;
  I: Integer;
begin
  Reader := THotPDF.Create(nil);
  try
    Reader.LoadFromFile('incoming-invoice.pdf');
    if HPDFValidateEInvoice(Reader, SchematronDir, Report) then
      Writeln('Container and business rules: OK')
    else
      Writeln('Validation found issues.');
    if Report.BusinessRulesEvaluated then
    begin
      Writeln(Report.BusinessSummary.Evaluated, ' rules evaluated, ',
        Report.BusinessSummary.Skipped, ' skipped (XPath 2.0)');
      for I := 0 to High(Report.BusinessRules) do
        if (not Report.BusinessRules[I].Skipped) and
           (Report.BusinessRules[I].Severity = stsError) then
          Writeln(string(Report.BusinessRules[I].RuleID), ': ',
            string(Report.BusinessRules[I].Message));
    end;
  finally
    Reader.Free;
  end;
end;

แต่ละรายการใน BusinessRules จะระบุตัวระบุกฎที่แยกออกมาจากข้อความยืนยัน — เช่น BR-45, BR-CO-17 และระบบการตั้งชื่ออื่นๆ ตามมาตรฐาน EN 16931 — ควบคู่ไปกับ XPath บริบทของกฎ, นิพจน์การทดสอบ และระดับความรุนแรงที่สะท้อนถึงแอตทริบิวต์แฟล็กของ Schematron สิ่งนี้ทำให้รายงานมีความเป็นรูปธรรมที่สามารถนำไปปฏิบัติการแก้ไขได้: แทนที่จะบอกแค่ผ่านหรือไม่ผ่าน คุณจะได้รับข้อมูลคำศัพท์ทางธุรกิจที่ขัดแย้งกันอย่างแน่ชัด ซึ่งเป็นข้อมูลที่ทีมสนับสนุนของคุณต้องการเมื่อลูกค้าสอบถามว่าเหตุใดใบแจ้งหนี้จึงถูกปฏิเสธ ทีมงานที่สร้างรายงานความสอดคล้องแบบมีโครงสร้างสำหรับเอกสาร PDF จัดเก็บอยู่แล้ว สามารถนำผลลัพธ์เหล่านี้ไปรวมเข้าในกระบวนการทำงานเดียวกันที่อธิบายไว้ในบทความเกี่ยวกับการทำระบบรายงานการตรวจสอบความพร้อมก่อนส่งพิมพ์ PDF (preflight reports) แบบอัตโนมัติ

ขอบเขตที่ตัวตรวจสอบความถูกต้องหยุดทำงานตามความเป็นจริง

เอนจิน Schematron ถูกสร้างขึ้นบน MSXML, ซึ่งจะประมวลผลคำสั่ง XPath 1.0 ไฟล์กฎ EN 16931 ประกาศการทำงาน queryBinding="xslt2" และบรรจุข้อความยืนยัน 424 รายการ ซึ่งส่วนใหญ่ใช้เฉพาะฟังก์ชัน XPath 1.0 เช่น string-length และ substring-after — ซึ่งสามารถรันแบบเนทีฟได้ ส่วนน้อยจะพึ่งพาโครงสร้างของ XPath 2.0 เช่น การแปลงค่า xs:decimal หรือฟังก์ชัน exists(); HotPDF จะสแกนนิพจน์การทดสอบล่วงหน้าเพื่อหาโทเค็นของ XPath 2.0 ที่รู้จัก และทำเครื่องหมายกฎเหล่านั้นว่า Skipped พร้อมระบุระดับความรุนแรงเป็นระดับข้อมูล (info) แทนที่จะพยายามประมวลผลซึ่งอาจทำให้เกิดการตีความผิดพลาดอย่างเงียบเชียบ รายงานสรุปจะแสดงจำนวนการประมวลผลและการข้ามแยกกัน เพื่อให้บันทึกของคุณระบุถึงจำนวนกฎที่ได้ทำการตรวจสอบอย่างชัดเจน บน Delphi 7 ที่ไม่มีการผูกเชื่อมต่อ MSXML ตัวยูนิตจะคอมไพล์โค้ดจำลองที่รายงานว่าเอนจินไม่พร้อมใช้งานโดยไม่รบกวนผลลัพธ์ของคอนเทนเนอร์; เอนจินแบบเต็มรูปแบบต้องใช้ Delphi XE2 ขึ้นไป

ข้อจำกัดประการที่สองมีความสำคัญยิ่งกว่า ผลลัพธ์ผ่าน (สีเขียว) จาก HPDFValidateEInvoice หมายถึงโครงสร้างคอนเทนเนอร์มีความถูกต้องและไม่มีการละเมิดกฎทางธุรกิจที่ได้รับการประมวลผล — ซึ่งไม่ใช่การรับรองความสอดคล้องทางกฎหมายภาษี กฎส่วนขยายของประเทศ, ข้อกำหนดเฉพาะของผู้รับ และภาระผูกพันทางกฎหมายเกี่ยวกับเนื้อหาใบแจ้งหนี้ล้วนอยู่ในระดับที่สูงกว่าแกนหลัก EN 16931 และอยู่นอกเหนือขอบเขตของไลบรารีใดๆ ให้ถือว่าระบบตรวจสอบความถูกต้องทำหน้าที่เป็นประตูกรองที่คัดกรองใบแจ้งหนี้ที่มีโครงสร้างชำรุดเสียหายออกจากกล่องข้อความขาออกของคุณ และทำเครื่องหมายเอกสารที่ขัดแย้งในกล่องข้อความขาเข้าของคุณ ให้รันระบบตรวจสอบ XPath 2.0 เต็มรูปแบบในกระบวนการทำงานของคุณเมื่อจำเป็นต้องได้รับการรับรอง และปล่อยให้ที่ปรึกษาด้านภาษีของคุณดูแลเรื่องความสอดคล้องทางกฎหมายภาษี APIs การฝัง, การตรวจจับ และการตรวจสอบความถูกต้องที่แสดงไว้ที่นี่เป็นส่วนหนึ่งของ HotPDF Component สำหรับ Delphi และ C++Builder พร้อมคลังตัวอย่าง E-Invoice ที่สมบูรณ์ซึ่งครอบคลุมโปรไฟล์ ZUGFeRD และ Factur-X ทั้ง 7 โปรไฟล์