Một hóa đơn điện tử tuân thủ không phải là tệp PDF với tệp XML đính kèm bên cạnh. Đó là một tài liệu PDF/A-3 duy nhất mang hóa đơn hai lần: một lần là trang mà con người đọc, và một lần là XML Cross Industry Invoice có thể đọc bằng máy được lưu trữ bên trong tệp dưới dạng tệp liên kết. Hai biểu diễn mô tả cùng một hóa đơn. Bản chất kép đó là điểm mấu chốt của các họ định dạng mà các quy định của châu Âu hiện yêu cầu, Factur-X ở Pháp và Đức, ZUGFeRD trên các thị trường nói tiếng Đức, và XRechnung cho thanh toán khu vực công của Đức. Bài viết này hướng dẫn cách PDFlibPas lắp ráp hóa đơn lai như vậy trong Delphi, những nơi tiêu chuẩn cho phép làm sai, và tại sao một hồ sơ trong danh mục cần một trình tạo XML hoàn toàn riêng biệt
Hóa đơn lai thực sự là gì
Trang hiển thị và XML nhúng phục vụ các độc giả khác nhau. Một nhân viên phê duyệt thanh toán nhìn vào trang đã được hiển thị. Hệ thống kế toán của bên mua tiếp nhận XML, đọc tổng số tiền và phân tích thuế dưới dạng các trường có cấu trúc, và ghi sổ mà không cần con người nhập bất cứ điều gì. Nội dung ngữ nghĩa của XML đó được quy định bởi EN 16931, tiêu chuẩn châu Âu định nghĩa mô hình dữ liệu hóa đơn: các trường nào tồn tại, ý nghĩa của chúng là gì, và trường nào là bắt buộc. EN 16931 là mô hình ngữ nghĩa, không phải định dạng tệp. Factur-X, ZUGFeRD 2.x, và XRechnung đều hiện thực hóa mô hình đó như một tài liệu Cross Industry Invoice của UN/CEFACT, cú pháp mang các trường EN 16931 trên đường truyền
Để tài liệu vừa có thể lưu trữ vừa tự mô tả, container là PDF/A-3, được định nghĩa bởi ISO 19005-3. PDF/A-3 là mức tuân thủ cho phép các tệp nhúng tùy ý, đây chính xác là những gì XML hóa đơn cần. PDF/A-2 cấm nhúng các tệp không phải là PDF/A, vì vậy hóa đơn Factur-X không thể là PDF/A-2. Do đó, việc chọn PDF/A-3 không phải là sở thích, mà là yêu cầu xuất phát trực tiếp từ việc muốn nhúng dữ liệu không phải PDF trong tài liệu lưu trữ
Tại sao quan hệ là Alternative
Nhúng các byte là phần dễ dàng. ISO 32000 §7.11.4 định nghĩa luồng tệp nhúng, đối tượng chứa XML thô và các tham số của nó. Phần làm cho tệp trở thành tệp liên kết hợp lệ là §14.13, thêm khái niệm về tệp liên kết và khóa /AFRelationship. Khóa đó nêu cách dữ liệu nhúng liên quan đến nội dung mà nó đính kèm, và giá trị mà Factur-X bắt buộc là Alternative
Lựa chọn này quan trọng vì các giá trị khác sẽ khẳng định điều gì đó sai về tài liệu. Source có nghĩa là XML là tài liệu gốc mà nội dung hiển thị được tạo ra từ đó, một bản gốc mà trang bắt nguồn từ đó. Supplement có nghĩa là XML thêm thông tin ngoài những gì trang hiển thị, một phần bổ sung không có trong bản hiển thị. Không cái nào là hóa đơn Factur-X. XML và trang là hai biểu diễn tương đương của một hóa đơn, mang cùng nội dung pháp lý ở hai dạng. Alternative là giá trị nói chính xác điều đó: một biểu diễn thay thế tương đương của nội dung hiển thị. Bộ xác thực đọc bất kỳ quan hệ nào khác trên tệp Factur-X sẽ từ chối nó, và đúng vậy, vì quan hệ là tuyên bố có thể đọc bằng máy về mục đích của tệp đính kèm
Danh mục hồ sơ
Mẫu hóa đơn điện tử đi kèm PDFlibPas chạy cùng đường dẫn tạo trên sáu hồ sơ, được định nghĩa là mảng các bản ghi trong InvoiceModel.pas. Mỗi hồ sơ mang các giá trị mà trình viết cần: tên hiển thị, tên tệp nhúng, mức tuân thủ, /AFRelationship, phiên bản, mã quốc gia tùy chọn, và URN GuidelineID mà XML thông báo bên trong ngữ cảnh tài liệu của nó
Sáu hồ sơ đó là Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED cho nước Pháp, XRechnung 3.0, ZUGFeRD 1.0 COMFORT, và ZUGFeRD 2.0 BASIC. GuidelineID là trường cho bên nhận biết chính xác hồ sơ nào để mong đợi, và các giá trị rất cụ thể. Factur-X EN16931 thông báo urn:cen.eu:en16931:2017. XRechnung 3.0 thông báo urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. ZUGFeRD 2.0 BASIC thông báo urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. Tên tệp nhúng cũng là một phần của hợp đồng. Các hồ sơ Factur-X nhúng factur-x.xml, XRechnung nhúng xrechnung.xml, và các hồ sơ ZUGFeRD nhúng ZUGFeRD-invoice.xml hoặc zugferd-invoice.xml. Bên nhận quét tên tệp đính kèm để tìm hóa đơn, vì vậy tên tệp không phải là trang trí
Một chi tiết trong danh mục đáng đọc kỹ. Hầu hết các hồ sơ sử dụng quan hệ Alternative, nhưng mục XRechnung 3.0 trong mẫu sử dụng Source. Hai định dạng trả lời cho các bộ xác thực và quy ước khác nhau, và mẫu thiết lập quan hệ của mỗi hồ sơ từ danh mục thay vì mã hóa cứng một giá trị duy nhất, đó là lý do tại sao trường theo từng hồ sơ tồn tại thay vì một hằng số
Bẫy ZUGFeRD 1.0
Thật hấp dẫn khi giả định rằng mọi hồ sơ đều là Cross Industry Invoice EN 16931 với các biến thể nhỏ về số lượng trường tùy chọn bạn điền. Điều đó đúng với năm trong sáu hồ sơ. Nó không đúng với ZUGFeRD 1.0 COMFORT, và lý do mang tính cấu trúc chứ không phải trang trí
Các hồ sơ hiện đại phát ra Cross Industry Invoice UN/CEFACT với phiên bản namespace :100, có phần tử gốc là rsm:CrossIndustryInvoice. ZUGFeRD 1.0 có trước lược đồ đó. Đó là CrossIndustryDocument năm 2014 với phiên bản namespace :1p0, và phần tử gốc của nó là rsm:CrossIndustryDocument. Các URN namespace khác nhau, phần tử gốc khác nhau, và cây phần tử khác nhau xuyên suốt: lược đồ :1p0 nhóm dữ liệu theo ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery, và ApplicableSupplyChainTradeSettlement, trong khi :100 sử dụng ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery, và ApplicableHeaderTradeSettlement. Tên tương tự nhau đủ để gây hiểu nhầm và đủ khác nhau để gây lỗi
Từ COMFORT trong tên hồ sơ mô tả mức độ phong phú của dữ liệu, một hồ sơ cấp độ tự động hóa với đầy đủ các mục chi tiết, phân tích thuế và điều khoản thanh toán, chứ không phải lược đồ nào mang nó. Vì vậy, bạn không thể lấy tài liệu :100 và dán nhãn lại cho ZUGFeRD 1.0. Mẫu xử lý điều này với một cờ trên mỗi bản ghi hồ sơ và hai hàm xây dựng riêng biệt, chọn đúng hàm trước khi bất kỳ XML nào được tạo
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;
Sự tách biệt không phải là tiện lợi triển khai. Đưa cây :100 cho bên nhận ZUGFeRD 1.0 tạo ra tài liệu thất bại xác thực lược đồ ở phần tử gốc, vì vậy hai họ phải được xây dựng bởi mã biết mình đang viết cái nào
Chọn mức PDF/A-3
PDF/A-3 có ba mức tuân thủ, và PDFlibPas chọn chúng thông qua SetPDFAMode. Chế độ 5 là PDF/A-3b, mức đảm bảo tái tạo hình ảnh đáng tin cậy. Chế độ 6 là PDF/A-3a, thêm các yêu cầu cấu trúc được gắn thẻ và khả năng tiếp cận của mức a. Chế độ 7 là PDF/A-3u, yêu cầu tất cả văn bản được ánh xạ sang Unicode. Bật chế độ cũng nhúng output intent sRGB tích hợp của thư viện, đặc trưng màu mà PDF/A yêu cầu để màu được hiển thị được xác định chứ không phụ thuộc thiết bị
Hầu hết các luồng hóa đơn chạy ở mức 3b, đủ để có trang hiển thị trung thực cộng với XML nhúng. Nếu bạn cần một hồ sơ ICC rõ ràng thay vì hồ sơ tích hợp, LoadOutputIntentProfile thay thế nó sau khi chế độ được thiết lập. Mẫu tải hồ sơ sRGB kho lưu trữ theo cách này và dùng intent tích hợp khi tệp không thể truy cập, vì vậy output intent luôn hiện diện
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;
Xây dựng hóa đơn lai
Với container đã được cấu hình, phần còn lại là ba bước theo thứ tự: thiết lập chế độ PDF/A-3, vẽ trang có thể đọc bằng con người, sau đó đính kèm XML dưới dạng tệp liên kết. Trang hiển thị là nội dung thông thường. Một ràng buộc đáng nhớ là PDF/A cấm các phông chữ Standard 14 không nhúng, vì vậy trang phải nhúng một gương mặt phông chữ thực thay vì tham chiếu đến một phông tích hợp
Đính kèm là một lời gọi duy nhất. AddFacturXAssociatedFileFromString lấy các byte XML UTF-8 thô cộng với siêu dữ liệu hồ sơ, ghi luồng tệp nhúng, đăng ký nó trong mảng /AF của Catalog mà PDF/A-3 yêu cầu, áp dụng /AFRelationship, và tạo siêu dữ liệu XMP hóa đơn điện tử xác định tài liệu là Factur-X, ZUGFeRD, hoặc XRechnung. Nó cũng kiểm tra xem GuidelineID của XML có khớp với mức tuân thủ bạn yêu cầu không, vì vậy sự không khớp giữa XML bạn xây dựng và hồ sơ bạn đặt tên sẽ bị bắt thay vì được gửi đi im lặng
// 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);
Một điều tinh tế trong đường dẫn dữ liệu là mã hóa. XML nhúng khai báo encoding="UTF-8", và phương thức lấy các byte của nó dưới dạng AnsiString, vì vậy tên người bán hay người mua không phải ASCII phải đến lời gọi dưới dạng byte UTF-8 thô. Một phép chuyển đổi đơn giản qua trang mã ANSI của hệ thống sẽ làm hỏng các ký tự đó và tạo ra hóa đơn có XML không còn khớp với khai báo của chính nó. Mẫu mã hóa sang UTF-8 một cách rõ ràng trước khi chuyển các byte, đây là cách an toàn để đưa dữ liệu vào bất kỳ API PDF nào hướng byte từ chuỗi Unicode
Để đính kèm XML không phải là hồ sơ hóa đơn điện tử được nhận ra, AddPDFA3AssociatedFileFromString là đối tác chung. Nó lấy tên tệp, loại MIME, mô tả, quan hệ và các byte, đồng thời ghi tệp liên kết PDF/A-3 thuần túy mà không có bất kỳ siêu dữ liệu hóa đơn cụ thể nào hay kiểm tra hướng dẫn. Sử dụng nó cho dữ liệu bổ sung; sử dụng phương thức Factur-X cho hóa đơn, để siêu dữ liệu hồ sơ và kết quả khớp hướng dẫn được ghi cho bạn
Khi tài liệu được tạo ra, câu hỏi tiếp theo là liệu nó có vượt qua xác thực PDF/A và khả năng tiếp cận không, và liệu nó có thể được ký mà không phá vỡ tuân thủ không. Những điều đó được đề cập trong hướng dẫn preflighting PDF/A và PDF/UA và bàn làm việc tuân thủ và ký. Tất cả những điều này đều đi kèm như một phần của PDFlibPas Delphi PDF Library, cùng với các API PDF/A, gắn thẻ và thuộc tính tài liệu mà đường dẫn hóa đơn điện tử xây dựng trên đó