horstoeko / zugferd
用于创建和读取欧洲电子发票的库
Requires
- php: ^7.3|^7.4|^8
- ext-simplexml: *
- goetas-webservices/xsd2php-runtime: ^0.2.13
- horstoeko/mimedb: ^1
- horstoeko/stringmanagement: ^1
- jms/serializer: ^3
- setasign/fpdf: ^1
- setasign/fpdi: ^2
- smalot/pdfparser: ^0|^2
- symfony/process: ^5|^6|^7
- symfony/validator: ^5|^6|^7
- symfony/yaml: ^5|^6|^7
Requires (Dev)
- dev-master
- v1.0.61
- v1.0.60
- v1.0.59
- v1.0.58
- v1.0.57
- v1.0.56
- v1.0.55
- v1.0.54
- v1.0.53
- v1.0.52
- v1.0.51
- v1.0.50
- v1.0.49
- v1.0.48
- v1.0.47
- v1.0.46
- v1.0.45
- v1.0.44
- v1.0.43
- v1.0.42
- v1.0.41
- v1.0.40
- v1.0.39
- v1.0.38
- v1.0.37
- v1.0.36
- v1.0.35
- v1.0.34
- v1.0.33
- v1.0.32
- v1.0.31
- v1.0.30
- v1.0.29
- v1.0.28
- v1.0.27
- v1.0.26
- v1.0.25
- v1.0.24
- v1.0.23
- v1.0.22
- v1.0.21
- v1.0.20
- v1.0.19
- v1.0.18
- v1.0.17
- v1.0.16
- v1.0.15
- v1.0.14
- v1.0.13
- v1.0.12
- v1.0.11
- v1.0.10
- v1.0.9
- v1.0.8
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.2
- v1.0.1
- v0.1.28
- v0.1.27
- v0.1.26
- v0.1.25
- v0.1.24
- v0.1.23
- v0.1.22
- v0.1.21
- v0.1.20
- v0.1.19
- v0.1.18
- v0.1.17
- v0.1.16
- v0.1.15
- v0.1.14
- v0.1.13
- v0.1.12
- v0.1.11
- dev-zf23
This package is auto-updated.
Last update: 2024-09-21 11:45:21 UTC
README
目录
许可证
本项目的代码在MIT许可证下提供。
概述
使用horstoeko/zugferd
,您可以读取和写入包含电子发票数据的xml文件,支持Minimum-, Basic-, EN16931-, Extended-和XRechnung配置文件。此外,还可以将XML数据附加到由ERP系统创建的现有PDF文件。如果同时存在XML文件(或XML字符串)和PDF文件(或字符串形式的PDF),则可以使用ZugferdDocumentPdfMerger
类创建一个符合规定的带附件的PDF文件。
这个库的优势是您不需要担心特定XML元素是否存在于所需的配置文件中 - 您可以使用相同的程序代码为所有支持的配置文件编写。
支持的配置文件
- EN16931 Minimum
- EN16931 Basic
- EN16931 Basic WL
- EN16931 Comfort
- EN16931 Extended
- EN16931 XRechnung 1.x
- EN16931 XRechnung 2.x
- EN16931 XRechnung 3.x
重要
此包仅提供对CII-Syntax的支持,而不是UBL-Syntax
更多信息
相关项目
依赖关系
本包使用了以下组件
我们的Wiki
我们提供了一个正在建设的Wiki。这个Wiki来源于您的问题,也来源于您的合作。如果在您使用此库的过程中发现某些内容不清楚或根本没有描述,请告诉我们。
安装
安装horstoeko/zugferd
的推荐方式是通过Composer
- 将依赖项添加到您的
composer.json
文件中
"require": { .. "horstoeko/zugferd":"^1", .. },
使用
有关详细说明,您可以查看此包的示例以及每个版本附带的文档。
配置
您可以通过多种方式配置此库。有关更多信息,请访问我们的Wiki。
读取xml文件
读取XML数据的中心入口是类ZugferdDocumentReader
。它提供了读取头信息和行信息的方法,如下例所示
use horstoeko\zugferd\ZugferdDocumentReader; $document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/xml/factur-x.xml"); $document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod); echo "\r\nGeneral document information\r\n"; echo "----------------------------------------------------------------------\r\n"; echo "Profile: {$document->getProfileDefinitionParameter("name")}\r\n"; echo "Profile: {$document->getProfileDefinitionParameter("altname")}\r\n"; echo "Document No: {$documentno}\r\n"; echo "Document Type: {$documenttypecode}\r\n"; echo "Document Date: {$documentdate->format("Y-m-d")}\r\n"; echo "Invoice currency: {$invoiceCurrency}\r\n"; echo "Tax currency: {$taxCurrency}\r\n"; if ($document->firstDocumentPosition()) { echo "\r\nDocument positions\r\n"; echo "----------------------------------------------------------------------\r\n"; do { $document->getDocumentPositionGenerals($lineid, $linestatuscode, $linestatusreasoncode); $document->getDocumentPositionProductDetails($prodname, $proddesc, $prodsellerid, $prodbuyerid, $prodglobalidtype, $prodglobalid); $document->getDocumentPositionGrossPrice($grosspriceamount, $grosspricebasisquantity, $grosspricebasisquantityunitcode); $document->getDocumentPositionNetPrice($netpriceamount, $netpricebasisquantity, $netpricebasisquantityunitcode); $document->getDocumentPositionLineSummation($lineTotalAmount, $totalAllowanceChargeAmount); $document->getDocumentPositionQuantity($billedquantity, $billedquantityunitcode, $chargeFreeQuantity, $chargeFreeQuantityunitcode, $packageQuantity, $packageQuantityunitcode); echo " - Line Id: {$lineid}\r\n"; echo " - Product Name: {$prodname}\r\n"; echo " - Product Description: {$proddesc}\r\n"; echo " - Product Buyer ID: {$prodbuyerid}\r\n"; echo " - Product Gross Price: {$grosspriceamount}\r\n"; echo " - Product Gross Price Basis Qty.: {$grosspricebasisquantity} {$grosspricebasisquantityunitcode}\r\n"; echo " - Product Net Price: {$netpriceamount}\r\n"; echo " - Product Net Price Basis Qty.: {$netpricebasisquantity} {$netpricebasisquantityunitcode}\r\n"; echo " - Quantity: {$billedquantity} {$billedquantityunitcode}\r\n"; echo " - Line amount: {$lineTotalAmount}\r\n"; if ($document->firstDocumentPositionTax()) { echo " - Position Tax(es)\r\n"; do { $document->getDocumentPositionTax($categoryCode, $typeCode, $rateApplicablePercent, $calculatedAmount, $exemptionReason, $exemptionReasonCode); echo " - Tax category code: {$categoryCode}\r\n"; echo " - Tax type code: {$typeCode}\r\n"; echo " - Tax percent: {$rateApplicablePercent}\r\n"; echo " - Tax amount: {$calculatedAmount}\r\n"; } while ($document->nextDocumentPositionTax()); } if ($document->firstDocumentPositionAllowanceCharge()) { echo " - Position Allowance(s)/Charge(s)\r\n"; do { $document->getDocumentPositionAllowanceCharge($actualAmount, $isCharge, $calculationPercent, $basisAmount, $reason, $taxTypeCode, $taxCategoryCode, $rateApplicablePercent, $sequence, $basisQuantity, $basisQuantityUnitCode, $reasonCode); echo " - Information\r\n"; echo " - Actual Amount: {$actualAmount}\r\n"; echo " - Type: " . ($isCharge ? "Charge" : "Allowance") . "\r\n"; echo " - Tax category code: {$taxCategoryCode}\r\n"; echo " - Tax type code: {$taxTypeCode}\r\n"; echo " - Tax percent: {$rateApplicablePercent}\r\n"; echo " - Calculated percent: {$calculationPercent}\r\n"; echo " - Basis amount: {$basisAmount}\r\n"; echo " - Basis qty.: {$basisQuantity} {$basisQuantityUnitCode}\r\n"; } while ($document->nextDocumentPositionAllowanceCharge()); } echo "\r\n"; } while ($document->nextDocumentPosition()); } if ($document->firstDocumentAllowanceCharge()) { echo "\r\nDocument allowance(s)/charge(s)\r\n"; echo "----------------------------------------------------------------------\r\n"; do { $document->getDocumentAllowanceCharge($actualAmount, $isCharge, $taxCategoryCode, $taxTypeCode, $rateApplicablePercent, $sequence, $calculationPercent, $basisAmount, $basisQuantity, $basisQuantityUnitCode, $reasonCode, $reason); echo " - Information\r\n"; echo " - Actual Amount: {$actualAmount}\r\n"; echo " - Type: " . ($isCharge ? "Charge" : "Allowance") . "\r\n"; echo " - Tax category code: {$taxCategoryCode}\r\n"; echo " - Tax type code: {$taxTypeCode}\r\n"; echo " - Tax percent: {$rateApplicablePercent}\r\n"; echo " - Calculated percent: {$calculationPercent}\r\n"; echo " - Basis amount: {$basisAmount}\r\n"; echo " - Basis qty.: {$basisQuantity} {$basisQuantityUnitCode}\r\n"; } while ($document->nextDocumentAllowanceCharge()); } if ($document->firstDocumentTax()) { echo "\r\nDocument tax\r\n"; echo "----------------------------------------------------------------------\r\n"; do { $document->getDocumentTax($categoryCode, $typeCode, $basisAmount, $calculatedAmount, $rateApplicablePercent, $exemptionReason, $exemptionReasonCode, $lineTotalBasisAmount, $allowanceChargeBasisAmount, $taxPointDate, $dueDateTypeCode); echo " - Information\r\n"; echo " - Tax category code: {$categoryCode}\r\n"; echo " - Tax type code: {$typeCode}\r\n"; echo " - Basis amount: {$basisAmount}\r\n"; echo " - Line total Basis amount: {$lineTotalBasisAmount}\r\n"; echo " - Tax percent: {$rateApplicablePercent}\r\n"; echo " - Tax amount: {$calculatedAmount}\r\n"; } while ($document->nextDocumentTax()); } $document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount); echo "\r\nDocument summation\r\n"; echo "----------------------------------------------------------------------\r\n"; echo " - Line total amount {$lineTotalAmount}\r\n"; echo " - Charge total amount {$chargeTotalAmount}\r\n"; echo " - Allowance total amount {$allowanceTotalAmount}\r\n"; echo " - Tax basis total amount {$taxBasisTotalAmount}\r\n"; echo " - Tax total amount {$taxTotalAmount}\r\n"; echo " - Grant total amount {$grandTotalAmount}\r\n"; echo " - Due payable amount {$duePayableAmount}\r\n";
读取带有xml附件的pdf文件
从PDF中读取发票数据类似于:您只需使用ZugferdDocumentPdfReader
类代替ZugferdDocumentReader
类
use horstoeko\zugferd\ZugferdDocumentPdfReader; $document = ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/xml/factur-x.pdf");
然后,读取发票数据的进一步操作与读取XML文件相同
写入xml文件
ZugferdDocumentBuilder
类再次是生成符合规定的XML数据的中心入口点
use horstoeko\zugferd\ZugferdDocumentBuilder; use horstoeko\zugferd\ZugferdProfiles; // Create an empty invoice document in the EN16931 profile $document = ZugferdDocumentBuilder::CreateNew(ZugferdProfiles::PROFILE_EN16931); // Add invoice and position information $document ->setDocumentInformation("471102", "380", \DateTime::createFromFormat("Ymd", "20180305"), "EUR") ->setDocumentBusinessProcess('urn:fdc:peppol.eu:2017:poacc:billing:01:1.0') ->addDocumentNote('Rechnung gemäß Bestellung vom 01.03.2018.') ->setDocumentSupplyChainEvent(\DateTime::createFromFormat('Ymd', '20180305')) ->setDocumentSeller("Lieferant GmbH", "549910") ->addDocumentSellerGlobalId("4000001123452", "0088") ->addDocumentSellerTaxRegistration("FC", "201/113/40209") ->addDocumentSellerTaxRegistration("VA", "DE123456789") ->setDocumentSellerAddress("Lieferantenstraße 20", "", "", "80333", "München", "DE") ->setDocumentSellerCommunication('EM', '[email protected]') ->setDocumentSellerContact('Horst Oeko', 'Financials', '0800-5726252', '0800-5726252', '[email protected]') ->setDocumentBuyer("Kunden AG Mitte", "GE2020211") ->setDocumentBuyerAddress("Kundenstraße 15", "", "", "69876", "Frankfurt", "DE") ->setDocumentBuyerCommunication('EM', '[email protected]') ->addDocumentPaymentMeanToDirectDebit('DE02120300000000202051', '471102') ->setDocumentBuyerReference('04011000-12345ABCXYZ-86') ->addDocumentTax("S", "VAT", 275.0, 19.25, 7.0) ->addDocumentTax("S", "VAT", 198.0, 37.62, 19.0) ->setDocumentSummation(529.87, 529.87, 473.00, 0.0, 0.0, 473.00, 56.87, null, 0.0) ->addDocumentPaymentTerm("Zahlbar innerhalb 30 Tagen netto bis 04.04.2018, 3% Skonto innerhalb 10 Tagen bis 15.03.2018", null, '549910') ->addNewPosition("1") ->setDocumentPositionProductDetails("Trennblätter A4", "", "TB100A4", null, "0160", "4012345001235") ->setDocumentPositionNetPrice(9.9000) ->setDocumentPositionQuantity(20, "H87") ->addDocumentPositionTax('S', 'VAT', 19) ->setDocumentPositionLineSummation(198.0) ->addNewPosition("2") ->setDocumentPositionProductDetails("Joghurt Banane", "", "ARNR2", null, "0160", "4000050986428") ->SetDocumentPositionNetPrice(5.5000) ->SetDocumentPositionQuantity(50, "H87") ->AddDocumentPositionTax('S', 'VAT', 7) ->SetDocumentPositionLineSummation(275.0) ->writeFile(dirname(__FILE__) . "/factur-x.xml");
写入带有附加xml文件的pdf文件
如果您已经有一个现有的发票打印输出(例如来自ERP系统)并希望向现有的PDF添加XML数据流,请使用类ZugferdDocumentPdfBuilder
use horstoeko\zugferd\ZugferdDocumentBuilder; use horstoeko\zugferd\ZugferdDocumentPdfBuilder; use horstoeko\zugferd\ZugferdProfiles; // Create an empty invoice document in the EN16931 profile $document = ZugferdDocumentBuilder::CreateNew(ZugferdProfiles::PROFILE_EN16931); // Add invoice and position information $document ->setDocumentInformation("471102", "380", \DateTime::createFromFormat("Ymd", "20180305"), "EUR") ->setDocumentBusinessProcess('urn:fdc:peppol.eu:2017:poacc:billing:01:1.0') ->addDocumentNote('Rechnung gemäß Bestellung vom 01.03.2018.') ->setDocumentSupplyChainEvent(\DateTime::createFromFormat('Ymd', '20180305')) ->setDocumentSeller("Lieferant GmbH", "549910") ->addDocumentSellerGlobalId("4000001123452", "0088") ->addDocumentSellerTaxRegistration("FC", "201/113/40209") ->addDocumentSellerTaxRegistration("VA", "DE123456789") ->setDocumentSellerAddress("Lieferantenstraße 20", "", "", "80333", "München", "DE") ->setDocumentSellerCommunication('EM', '[email protected]') ->setDocumentSellerContact('Horst Oeko', 'Financials', '0800-5726252', '0800-5726252', '[email protected]') ->setDocumentBuyer("Kunden AG Mitte", "GE2020211") ->setDocumentBuyerAddress("Kundenstraße 15", "", "", "69876", "Frankfurt", "DE") ->setDocumentBuyerCommunication('EM', '[email protected]') ->addDocumentPaymentMeanToDirectDebit('DE02120300000000202051', '471102') ->setDocumentBuyerReference('04011000-12345ABCXYZ-86') ->addDocumentTax("S", "VAT", 275.0, 19.25, 7.0) ->addDocumentTax("S", "VAT", 198.0, 37.62, 19.0) ->setDocumentSummation(529.87, 529.87, 473.00, 0.0, 0.0, 473.00, 56.87, null, 0.0) ->addDocumentPaymentTerm("Zahlbar innerhalb 30 Tagen netto bis 04.04.2018, 3% Skonto innerhalb 10 Tagen bis 15.03.2018", null, '549910') ->addNewPosition("1") ->setDocumentPositionProductDetails("Trennblätter A4", "", "TB100A4", null, "0160", "4012345001235") ->setDocumentPositionNetPrice(9.9000) ->setDocumentPositionQuantity(20, "H87") ->addDocumentPositionTax('S', 'VAT', 19) ->setDocumentPositionLineSummation(198.0) ->addNewPosition("2") ->setDocumentPositionProductDetails("Joghurt Banane", "", "ARNR2", null, "0160", "4000050986428") ->SetDocumentPositionNetPrice(5.5000) ->SetDocumentPositionQuantity(50, "H87") ->AddDocumentPositionTax('S', 'VAT', 7) ->SetDocumentPositionLineSummation(275.0); // Save merged PDF (existing original and XML) to a file $pdfBuilder = new ZugferdDocumentPdfBuilder($document, "/tmp/existingprintlayout.pdf"); $pdfBuilder->generateDocument()->saveDocument("/tmp/merged.pdf"); // Alternatively, you can also return the merged output (existing original and XML) as a binary string $pdfBuilder = new ZugferdDocumentPdfBuilder($document, "/tmp/existingprintlayout.pdf"); $pdfBinaryString = $pdfBuilder->generateDocument()->downloadString("merged.pdf");
合并现有PDF和XML
假设我们已经有了一个符合规定的XML(例如在Comfort配置文件中)和一个已经包含打印布局的PDF。然后可以使用类ZugferdDocumentPdfMerger
将这两个文件合并成一个符合规定的PDF(带有XML附件)。
use horstoeko\zugferd\ZugferdDocumentPdfMerger; require dirname(__FILE__) . "/../vendor/autoload.php"; $existingXml = dirname(__FILE__) . "/invoice_1.xml"; $existingPdf = dirname(__FILE__) . "/emptypdf.pdf"; $mergeToPdf = dirname(__FILE__) . "/fullpdf.pdf"; if (!file_exists($existingXml) || !file_exists($existingPdf)) { throw new \Exception("XML and/or PDF does not exist"); } (new ZugferdDocumentPdfMerger($existingXml, $existingPdf))->generateDocument()->saveDocument($mergeToPdf);
XML和/或PDF不必以文件形式提供。也可以将包含相应数据的字符串传递给ZugferdDocumentPdfMerger
类。
use horstoeko\zugferd\ZugferdDocumentPdfMerger; require dirname(__FILE__) . "/../vendor/autoload.php"; $existingXml = "<xml>,,,,,</xml>"; $existingPdf = "%PDF-1.5..........."; $mergeToPdf = dirname(__FILE__) . "/fullpdf.pdf"; (new ZugferdDocumentPdfMerger($existingXml, $existingPdf))->generateDocument()->saveDocument($mergeToPdf);
验证
此库提供了一些检查和验证文档的选项。请访问我们Wiki中的相应页面。