ใบแจ้งหนี้แบบ Factur-X หรือ ZUGFeRD คือเอกสารสองฉบับที่สวมใส่ชื่อไฟล์เดียว เอกสารชั้นนอกคือคอนเทนเนอร์แบบ PDF/A-3 ซึ่งโปรแกรมอ่านสำหรับการเก็บถาวร (archival reader) จะต้องยอมรับไปอีกสิบปีข้างหน้า เอกสารชั้นในคือใบแจ้งหนี้แบบ XML ที่ระบบบัญชีของผู้ซื้อจะต้องแยกวิเคราะห์ (parse) เทียบกับ EN 16931 ความผิดพลาดที่ส่งใบแจ้งหนี้ที่พังๆ เข้าสู่การผลิตก็คือความเชื่อที่ว่า การทำสิ่งแรกให้ถูกต้องก็จะทำให้ได้สิ่งที่สองมาแบบฟรีๆ แต่มันไม่ใช่ ไฟล์ๆ หนึ่งสามารถเป็น PDF/A-3 ที่ไร้ที่ติ และยังคงบรรทุก XML ที่ไม่มีหน่วยงานด้านภาษีใดๆ จะยอมรับ และมันสามารถบรรทุกตำราเรียนที่เป็น XML ของ EN 16931 ไว้ภายในคอนเทนเนอร์ที่ไม่ผ่านการตรวจสอบการเก็บถาวร เลเยอร์ (layers) ทั้งสองถูกตรวจสอบโดยเครื่องมือสองตัวที่แตกต่างกันและไม่รู้จักกันเลย และไพพ์ไลน์ (pipeline) ที่ใช้งานจริงก็จะต้องตอบสนองให้ได้ทั้งคู่
ตัวตรวจสอบความถูกต้องสองตัว, คำถามสองข้อที่แตกต่างกัน
veraPDF คือการอิมพลีเมนต์อ้างอิง (reference implementation) สำหรับ PDF/A ชี้มันไปที่ใบแจ้งหนี้แล้วมันจะตอบคำถามเพียงข้อเดียว: นี่คือไฟล์ PDF/A-3 ที่ปฏิบัติตามข้อกำหนดหรือไม่ มันตรวจสอบสิ่งที่ ISO 19005-3 ให้ความใส่ใจ ฟอนต์ทุกตัวถูกฝังไว้หรือไม่ มี OutputIntent หรือไม่ เมทาดาตา (metadata) ของ XMP มีการประกาศส่วนและระดับการปฏิบัติตามข้อกำหนดที่ถูกต้องหรือไม่ สำหรับ e-invoice มันจะทำการตรวจสอบระบบประปา (plumbing) ของไฟล์ที่เกี่ยวข้องซึ่ง PDF/A-3 ต้องการด้วย เนื่องจาก XML เดินทางไปในฐานะไฟล์ที่ถูกฝังไว้พร้อมกับ /AFRelationship และรายการหนึ่งในอาร์เรย์ (array) /AF ของแค็ตตาล็อกเอกสาร veraPDF ไม่ได้บอกอะไรเลยเกี่ยวกับว่าผลรวมของใบแจ้งหนี้นั้นบวกออกมาแล้วถูกต้องหรือไม่ เพราะนั่นไม่ใช่ขอบเขตอำนาจของมัน
Mustang คือตัวตรวจสอบความถูกต้องแบบโอเพ่นซอร์ส (open-source validator) จาก Mustangproject มันตั้งคำถามที่ตั้งฉาก (orthogonal question) กัน: XML ที่ฝังอยู่นั้นเป็นใบแจ้งหนี้ที่ถูกต้องหรือไม่ มันจะรัน (run) ตัว XML เทียบกับสคีมา (schema) สำหรับโปรไฟล์ (profile) ที่ประกาศไว้ จากนั้นจะปรับใช้กฎทางธุรกิจของ EN 16931 และชุดกฎเฉพาะสำหรับประเทศที่ซ้อนทับกันอยู่ด้านบน โดยมี CIUS ของ XRechnung รวมอยู่ในนั้น มันตรวจสอบว่ามีหมายเลขประจำตัวผู้เสียภาษี (VAT identifier) ของผู้ขายอยู่ในเวลาที่ยอดรวมเรียกร้องให้ต้องมี ค่าเผื่อหนี้สงสัยจะสูญและจำนวนเงินที่เรียกเก็บนั้นสอดคล้องกับยอดรวมของเอกสารหรือไม่ URN ของโปรไฟล์ใน XML ตรงกับสิ่งที่ไฟล์กล่าวอ้างหรือไม่ Mustang ไม่สนใจว่า PDF ที่ล้อมรอบอยู่นั้นได้ฝังฟอนต์ของตนเอาไว้หรือไม่ เพราะนั่นเป็นงานของ veraPDF
เครื่องมือทั้งสองไม่ใช่ซูเปอร์เซ็ต (superset) ของอีกฝ่าย veraPDF ให้คอนเทนเนอร์ที่มีโครงสร้างสมบูรณ์แบบผ่านโดยที่มี XML ที่ไร้สาระอยู่รอบๆ Mustang ให้ XML ที่สมบูรณ์แบบที่ถูกห่ออยู่ในคอนเทนเนอร์ที่มี OutputIntent ขาดหายไปผ่าน ต่างฝ่ายต่างก็จับข้อบกพร่อง (defect) ในประเภทที่อีกฝ่ายตาบอดได้อย่างพอดิบพอดี ซึ่งนั่นคือเหตุผลทั้งหมดที่อุปกรณ์ตรวจสอบความถูกต้องแบบเอาจริงเอาจัง (serious validation harness) จะต้องรันเครื่องมือทั้งคู่ และปฏิบัติต่อไฟล์เสมือนว่ามันพร้อมจัดส่งก็ต่อเมื่อทั้งสองฝ่ายเห็นพ้องต้องกันเท่านั้น
เมทริกซ์การตรวจสอบความถูกต้อง (validation matrix)
เพื่อพิสูจน์ให้เห็นว่าไลบรารีทำการผลิตไฟล์ที่จะอยู่รอดจากประตูทั้งสองบาน อุปกรณ์ (harness) จึงสร้างเมทริกซ์ขึ้นมา โปรไฟล์ใบแจ้งหนี้หกรายการจะครอบคลุมขอบเขตของสิ่งที่ไพพ์ไลน์ในยุโรปจะต้องพบเจอในทางปฏิบัติ ได้แก่: 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) ด้วยเส้นทางโค้ดแบบเดียวกันกับที่ GUI sample จัดส่งไป ดังนั้นอาร์ติแฟกต์ (artifacts) ภายใต้การทดสอบจึงไม่ได้ถูกปรับแต่งด้วยมือ (hand-tuned) เพื่อการทดสอบแต่อย่างใด
ตัวสร้าง (generator) จะเขียนไฟล์ทั้งสิบสองไฟล์และสคริปต์ (script) จะป้อนแต่ละไฟล์ให้กับตัวตรวจสอบความถูกต้องทั้งสองตัว ในการรันเต็มรูปแบบครั้งแรก veraPDF ผ่านทั้งสิบสองไฟล์ ระบบประปาของคอนเทนเนอร์นั้นถูกต้องครอบคลุมไปทั้งหมด: ไฟล์ที่เกี่ยวข้องได้รับการลงทะเบียน การปฏิบัติตามข้อกำหนด XMP ได้รับการประกาศ เจตนาการส่งออกมีประจำตำแหน่งที่ Mustang ผ่านแปดไฟล์ ใบแจ้งหนี้สี่ใบเป็นไฟล์ PDF/A-3 ที่มีความถูกต้องทางโครงสร้างซึ่งบรรทุก XML ที่ตัวตรวจสอบกฎทางธุรกิจได้ปฏิเสธ ซึ่งนี่ก็คือการแบ่งหน้าที่ที่แนวทางแบบสองเครื่องมือมีตัวตนอยู่เพื่อให้มันปรากฏขึ้นมาได้อย่างพอดิบพอดี หากอุปกรณ์ (harness) ได้ทำการเชื่อใจ veraPDF เพียงอย่างเดียว ทั้งสี่ไฟล์นั้นก็จะดูเหมือนว่าเสร็จสมบูรณ์ไปแล้ว
การแก้ไขสองจุดที่ปิดช่องโหว่
ความล้มเหลวทั้งสี่ครั้งของ Mustang มาจากสาเหตุที่แตกต่างกันสองประการ และการแก้ไขแต่ละอย่างก็คือรายละเอียดที่คุ้มค่าแก่การรู้ไว้ก่อนที่คุณจะทำการสร้างโปรไฟล์เหล่านี้ด้วยตัวเอง
สิ่งแรกคือโปรไฟล์ Factur-X EXTENDED France B2B ตัวสร้าง (generator) ดั้งเดิมส่งผ่านป้ายกำกับภายในเป็นระดับการปฏิบัติตามข้อกำหนดและ 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 เฉพาะสำหรับฝรั่งเศสก็ยังคงเป็นเอกสารแบบโปรไฟล์ EXTENDED ในส่วนที่เกี่ยวกับเมทาดาตาของ XMP ลักษณะเฉพาะของใบแจ้งหนี้แบบฝรั่งเศสไม่ได้แสดงออกโดยการประดิษฐ์ค่าการปฏิบัติตามข้อกำหนดแบบที่หกขึ้นมา มันถูกแสดงออกโดยรหัสประเทศ (country code) คือ FR และโดยตัวระบุแนวทาง (guideline identifier) ที่อยู่ภายใน 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 บังคับให้การสรุปยอดการชำระเงินส่วนหัว (header settlement summation) นั่นคือ ram:SpecifiedTradeSettlementMonetarySummation จะต้องบรรจุ ram:ChargeTotalAmount และ ram:AllowanceTotalAmount โดยต้องมีอย่างละหนึ่งครั้งเท่านั้น XML ที่สร้างขึ้นได้ละเว้นทั้งสองส่วน ดังนั้น Mustang จึงรายงานว่าองค์ประกอบเหล่านั้นจะต้องเกิดขึ้นมาหนึ่งครั้งพอดี สิ่งเหล่านี้ไม่ใช่ทางเลือกเมื่อสคีมากล่าวว่า minOccurs คือหนึ่ง การปล่อย (emitting) ทั้งสองส่วนตามลำดับความต่อเนื่องของ XSD ทันทีหลังจาก ram:LineTotalAmount ด้วยค่าที่เป็น 0.00 เมื่อไม่มีค่าที่เรียกเก็บหรือค่าเผื่อเหลือเผื่อขาด ย่อมเป็นการตอบสนองต่อสคีมา ค่าศูนย์คือองค์ประกอบที่มีอยู่; องค์ประกอบที่หายไปคือการละเมิดสคีมา เมื่อมีการแก้ไขทั้งสองจุดนี้แล้ว เมทริกซ์ก็มุ่งหน้าสู่สิบสองเต็มสิบสองบน Mustang ในขณะที่ยังคงเป็นสิบสองเต็มสิบสองบน veraPDF
ฟิลด์ของ XRechnung ที่พลิกจากใช้ไม่ได้เป็นใช้ได้
XRechnung สมควรได้รับหมายเหตุของมันเอง เนื่องจาก CIUS ภาษาเยอรมันของมันได้เพิ่มกฎทางธุรกิจที่ขาดหายไปจากชุดฐาน EN 16931 เข้าไป และพวกมันก็ล้มเหลวในรูปแบบที่ดูเหมือนไม่มีอะไรผิดปกติกับเอกสารเลยหากมองเพียงผิวเผิน มีสองข้อที่เกี่ยวกับที่อยู่อิเล็กทรอนิกส์ (electronic addresses) BT-34 คือที่อยู่อิเล็กทรอนิกส์ของผู้ขาย และ BT-49 คือที่อยู่อิเล็กทรอนิกส์ของผู้ซื้อ ซึ่งเป็นจุดสิ้นสุดการกำหนดเส้นทาง (routing endpoints) ที่พอร์ทัลภาครัฐของเยอรมนีใช้เพื่อทำการส่งมอบและตอบรับใบแจ้งหนี้ โมเดล EN 16931 ฐานปฏิบัติต่อพวกมันเสมือนเป็นทางเลือก (optional) XRechnung กลับไม่เป็นเช่นนั้น หากละเว้นอันใดอันหนึ่ง ใบแจ้งหนี้ก็จะขึ้นรูปมาเป็นอย่างดี ถูกต้องตามสคีมา และจะถูกปฏิเสธ
ข้อที่สามคือกฎ BR-DE-6 ซึ่งบังคับว่าหมายเลขโทรศัพท์ติดต่อของผู้ขายจะต้องมีปรากฏอยู่ มันเป็นฟิลด์ประเภทที่นักพัฒนาชอบทิ้งไปเพราะมันให้ความรู้สึกเหมือนเป็นการนำเสนอมากกว่าที่จะเป็นข้อมูล และการที่มันหายไปก็จะสร้างความล้มเหลวในการตรวจสอบความถูกต้องซึ่งจะชี้ไปที่กลุ่มผู้ติดต่อฝ่ายผู้ขายแทนที่จะเป็นสิ่งที่ขาดหายไปอย่างเห็นได้ชัด การจัดหา BT-34, BT-49 และหมายเลขโทรศัพท์ของผู้ขายคือสิ่งที่ทำให้ไฟล์ของ XRechnung เปลี่ยนจากจุดที่ใช้ไม่ได้ไปสู่ความถูกต้องภายใต้ Mustang และทั้งหมดนี้ก็ไม่เปลี่ยนอะไรเลยที่ veraPDF มองเห็น เพราะทั้งสามตัวนี้ล้วนแต่อาศัยอยู่ใน XML ทั้งสิ้น
การเดินสาย (wiring) เอาต์พุตของไลบรารีไปยังตัวตรวจสอบความถูกต้อง
ประเด็นทางสถาปัตยกรรม (architectural point) ที่อยู่เบื้องหลังอุปกรณ์ (harness) นี้สามารถอ้างอิงนำไปใช้ทั่วๆ ไปกับระบบธุรกิจใดๆ ก็ได้ ไลบรารีของ PDF จะทำการเขียนคอนเทนเนอร์ที่ปฏิบัติตามข้อกำหนดและฝังตัว XML เข้าไป มันไม่พยายาม และไม่ควรพยายามที่จะเป็นหน่วยงานรับผิดชอบกฎทางธุรกิจของ EN 16931 ValidateFacturXInvoice ในไลบรารีจะทำการตรวจสอบความสอดคล้องกันของคอนเทนเนอร์ ว่าอาร์เรย์ (array) /AF ในแค็ตตาล็อก ลำดับขั้นของชื่อ (name tree) ไฟล์ที่ถูกฝังไว้ DocumentFileName ของ XMP โปรไฟล์ (profile) แนวทาง (guideline) และ /AFRelationship ล้วนลงรอยกัน แต่มันไม่ได้ทำการตรวจสอบรหัสภาษี หรือไกล่เกลี่ยยอดรวม การแบ่งงาน (division of labor) ที่ถูกต้องก็คือให้ระบบธุรกิจทำหน้าที่สกัดตัว XML ออกมาแล้วส่งมอบให้กับตัวตรวจสอบใบแจ้งหนี้ที่อุทิศตัวเฉพาะทาง เช่นเดียวกับที่อุปกรณ์ดังกล่าวมอบให้แก่ Mustang อย่างพอดิบพอดี
การอ่านไฟล์กลับมาจะบอกคุณได้ว่ามีอะไรที่ถูกเขียนลงไปจริงๆ บ้าง DetectFacturXInvoice จะรายงานว่ามีการรับรู้ถึงใบแจ้งหนี้หรือไม่ และ GetFacturXInvoiceInfo จะอ่านฟิลด์เมทาดาตา (metadata fields) ตามแท็ก (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 ซึ่งจะถูกเรียกใช้งานผ่านคำสั่งหน้าจอ (command-line) jar พร้อมกับที่ veraPDF จะถูกรันในเส้นทางผ่านเดียวกันทับไปบนไฟล์เดียวกัน การเดินสายนั้นมีขนาดเล็ก: ตัวสร้างคอนโซล (console generator) ซึ่งก็คือ EInvoiceValidation.dpr จะเขียนไฟล์ทั้งสิบสองไฟล์โดยใช้โมเดลของใบแจ้งหนี้แบบแชร์ (shared invoice model) จาก sample และมีสคริปต์คือ run-validation.ps1 จะผลักดันตัวตรวจสอบความถูกต้องทั้งสองตัวเหนือไดเรกทอรีเอาต์พุต (output directory) และพิมพ์ตารางของการผ่านและตก รูปร่างแบบสองขั้นตอนเดียวกัน ได้แก่ สร้างด้วยไลบรารีและตรวจสอบความถูกต้องด้วยตัวตรวจสอบภายนอก คือสิ่งที่งานที่มีการรวมระบบอย่างต่อเนื่อง (continuous-integration job) ควรจะรันทุกครั้งที่มีการเปลี่ยนแปลงในการสร้างใบแจ้งหนี้ เนื่องจากวิธีเดียวที่จะรู้ว่าไฟล์สามารถตอบสนองให้ได้ทั้งสองเลเยอร์ (layers) คือการถามเครื่องมือทั้งสองตัว
หากไพพ์ไลน์ (pipeline) ของคุณจะต้องรับรองคอนเทนเนอร์ก่อนที่จะลงลายเซ็น (signing) ในแง่มุมของการตรวจสอบก่อนบิน (preflight) ของงานนี้ได้รับการครอบคลุมไว้ใน คำแนะนำเบื้องต้นเกี่ยวกับ PDF/A และการตรวจสอบก่อนบินของ PDF/UA ใน Delphi ของเรา และโฟลว์ของการรับรองแล้วจึงลงลายเซ็น (certify-then-sign flow) ในมุมที่กว้างขึ้นได้รับการอธิบายไว้ใน ม้านั่งทำงานสำหรับการปฏิบัติตามข้อกำหนดและการลงลายเซ็น ทั้งคู่จะถูกสร้างขึ้นมาบนเส้นทางการสร้างเดียวกันซึ่งถูกจัดส่งมาให้ในฐานะเป็นส่วนหนึ่งของ Delphi PDF Library สำหรับ Delphi และ C++Builder ควบคู่ไปกับ PDF/A ไฟล์ที่เกี่ยวข้อง (associated-file) และ API เมทาดาตา (metadata APIs) ที่ใช้ ณ ที่นี้