From 48459533e18541fec3a2bb4ad9eb2f4e9c29fe15 Mon Sep 17 00:00:00 2001 From: sparkyx Date: Tue, 11 Nov 2025 10:59:14 +0100 Subject: [PATCH] e-invoice : make a PDF with credit note --- include/XMLDocument/xml_reader.class.php | 535 ++++++++++++++++++ include/XMLDocument/xmlcreditnote.class.php | 31 + .../xmlcreditnote_reader.class.php | 210 +++++++ .../XMLDocument/xmlinvoice_reader.class.php | 502 ++-------------- include/constant.php | 4 +- 5 files changed, 830 insertions(+), 452 deletions(-) create mode 100644 include/XMLDocument/xml_reader.class.php create mode 100644 include/XMLDocument/xmlcreditnote.class.php create mode 100644 include/XMLDocument/xmlcreditnote_reader.class.php diff --git a/include/XMLDocument/xml_reader.class.php b/include/XMLDocument/xml_reader.class.php new file mode 100644 index 000000000..b0d5623f2 --- /dev/null +++ b/include/XMLDocument/xml_reader.class.php @@ -0,0 +1,535 @@ +domDocument = $domDocument; + $this->xpath = new \DOMXPath($this->domDocument); + } + /** + * @brief before executing xpath->query the namespace must be registered + * the NS are different for each type of doc + * @param $xml (null , DOMXPath simpleXML) + */ + public function registerNS($xml=null) + { + $a_namespace=$this->get_namespace(); + if ($xml == null) + { + foreach ($a_namespace as $pf=>$ns) { + $this->xpath->registerNamespace($pf,$ns); + } + } + elseif ( get_class($xml) == "SimpleXMLElement" ) + { + foreach ($a_namespace as $pf=>$ns) { + $xml->registerXPathNamespace($pf,$ns); + } + } + elseif (get_class($xml) == "DOMXPath" ) + { + foreach ($a_namespace as $pf=>$ns) { + $xml->registerNamespace($pf,$ns); + } + + } + else + { + throw new \Exception ("unknown object ".get_class($xml),57); + } + } + /** + * + * @return \DOMDocument + */ + public function get_domDocument(): \DOMDocument + { + return $this->domDocument; + } + + public function get_xpath(): \DOMXPath + { + return $this->xpath; + } + + public function set_domDocument($domDocument) + { + $this->domDocument = $domDocument; + return $this; + } + + /** + * @brief Build an XMLInvoice_Reader object from an XML file + * @param $filename (string) file and path to the file + * @return \Noalyss\XMLDocument\XMLInvoice_Reader + * @throws \Exception if xml not valid + */ + abstract static function build_from_file($filename); + + /** + * @brief Build an XMLInvoice_Reader object from an XML string + * @param $string (string) XML + * @return \Noalyss\XMLDocument\XMLInvoice_Reader + * @throws \Exception if xml not valid + */ + abstract static function build_from_string($string); + + + + /** + * @brief returns the embedded document in an array (keys : filecontent (BYTES),mimecode , filename) + * or false if there is no document + * @return bool|array of Document_Reference + * @throws \Exception if there are several documents + * + */ + public function get_embedded_document() + { + $a_document_reference = array(); + foreach (array( + "AdditionalDocumentReference", + "ReceiptDocumentReference", + "StatementDocumentReference", + "OriginatorDocumentReference", + "ContractDocumentReference" + ) + as $document) + { + $document = $this->domDocument->getElementsByTagName($document); + if ($document->length == 0) + { + continue; + } + for ($e = 0; $e < $document->count(); $e++) + { + $item = $document->item($e); + + if ($item->nodeType == XML_ELEMENT_NODE) + { + $id = $item->getElementsByTagName("ID")[0]?->nodeValue; + $description = $item->getElementsByTagName("DocumentDescription")[0]?->nodeValue; + $embedded_document = $item->getElementsByTagName("EmbeddedDocumentBinaryObject"); + if ($embedded_document->length == 0) + { + continue; + } + $binary_object = new Binary_Object; + $binary_object->filecontent = base64_decode($embedded_document[0]->nodeValue); + $binary_object->mimecode = $embedded_document[0]->getAttribute("mimeCode"); + $binary_object->filename = $embedded_document[0]->getAttribute("filename"); + $document_reference = new Document_Reference(); + $document_reference->setId($id) + ->setDescription($description) + ->setBinary_object($binary_object); + $a_document_reference[] = clone $document_reference; + } + } + } + return $a_document_reference; + } + /** + * @brief execute a XPATH query of the DOMDocument and return the result + * or null if nothing found + * @param $query (string) valid XPath Query + * @return null or DOMNodeList or DOMElement + */ + public function get_node($query) + { + + $this->registerNS(); + if ($this->xpath->query($query)->length == 0) + { + return null; + } + return $this->xpath->query($query); + } + /** + * @brief get the node value + * @param $query (string) valid XPath query + * @param $ix (int) the item number + * @return string + */ + function get_node_value($query, $ix = 0) + { + return $this->get_node($query)?->item($ix)?->nodeValue; + } + + /** + * @brief get the customer info from XML + * @return array + */ + function get_customer(): array + { + $result = []; + $result['ID'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cbc:EndpointID[1]"); + $result['name'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PartyName[1]/cbc:Name[1]'); + $result['street'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:StreetName[1]'); + $result['city'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:CityName[1]'); + $result['postcode'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:PostalZone[1]'); + $result['country_code'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cac:Country[1]/cbc:IdentificationCode[1]"); + $result['company_id'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PartyTaxScheme[1]/cbc:CompanyID[1]"); + $result['scheme'] = $this->get_node("//cac:AccountingCustomerParty[1]/cac:Party[1]/cbc:EndpointID[1]")[0]->getAttribute("schemeID"); + return $result; + } + + /** + * @brief get the supplier info from XML + * @return array + */ + function get_supplier(): array + { + $result = []; + $result['ID'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cbc:EndpointID[1]"); + $result['name'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PartyName[1]/cbc:Name[1]"); + $result['street'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:StreetName[1]"); + $result['city'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:CityName[1]"); + $result['postcode'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:PostalZone[1]"); + $result['country_code'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cac:Country[1]/cbc:IdentificationCode[1]"); + $result['company_id'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PartyTaxScheme[1]/cbc:CompanyID[1]"); + $result['scheme'] = $this->get_node("//cac:AccountingSupplierParty[1]/cac:Party[1]/cbc:EndpointID[1]")[0]->getAttribute("schemeID"); + return $result; + } + + + + /** + * @brief get the Payment Means info from XML + * @return array + */ + function get_payment_mean(): array + { + $result = []; + $result['payment_code'] = $this->get_node_value("//cac:PaymentMeans[1]/cbc:PaymentMeansCode[1]"); + $result['label'] = $this->get_node_value("//cac:PaymentMeans[1]/cbc:PaymentID[1]"); + $result['iban'] = $this->get_node_value("//cac:PaymentMeans[1]/cac:PayeeFinancialAccount[1]/cbc:ID[1]"); + $result['bic'] = $this->get_node_value("//cac:PaymentMeans[1]/cac:PayeeFinancialAccount[1]/cac:FinancialInstitutionBranch[1]/cbc:ID[1]"); + $result['note'] = []; + $note = $this->get_node("//cac:PaymentTerms/cbc:Note"); + if ($note != null) + { + $a = $note->length; + for ($i = 0; $i < $a; $i++) + { + $result['note'][] = $note->item(0)->nodeValue; + } + } + /** + + In geval van betaling binnen 14 dagen is 2% (52.00€) betalingskorting van + toepassing en het te betalen bedrag = 3053.68€ + En cas de paiement dans les 14 jours, l'escompte conditionnel de 2% (52.00€) est appliqué et le montant payable = 3053.68€ + In case of payment within 14 days, 2% (52.00€) conditional cash/payment discount applies and the payable amount = 3053.68€ + + + */ + return $result; + } + + /** + * @brief get the node value from simpleXML + * @param $xml (\SimpleXMLElement) fragment of XML dom + * @param $query (string) XPath query + * @return string + */ + protected function get_simple_xml_value(\SimpleXMLElement $xml, $query): string + { + $this->registerNS($xml); + + $x = $xml->xpath($query); + $result = (count($x) == 0) ? "" : $x[0]; + return (string) $result; + } + + /** + * @brief get the amount summary info from XML + * @return array + */ + function get_amount_summary(): array + { + $result = []; + $node = $this->get_node("//cac:LegalMonetaryTotal"); + $xml = simplexml_import_dom($node->item(0)); + $result['LineExtensionAmount'] = $this->get_simple_xml_value($xml, "cbc:LineExtensionAmount"); + $result['TaxExclusiveAmount'] = $this->get_simple_xml_value($xml, "cbc:TaxExclusiveAmount"); + $result['TaxInclusiveAmount'] = $this->get_simple_xml_value($xml, "cbc:TaxInclusiveAmount"); + $result['PayableAmount'] = $this->get_simple_xml_value($xml, "cbc:PayableAmount"); + $result['AllowanceTotalAmount'] = $this->get_simple_xml_value($xml, "cbc:AllowanceTotalAmount"); + $result['AllowanceTotalAmount'] = ($result['AllowanceTotalAmount'] == "") ? 0 : $result['AllowanceTotalAmount']; + $result['ChargeTotalAmount'] = $this->get_simple_xml_value($xml, "cbc:ChargeTotalAmount"); + $result['ChargeTotalAmount'] = ($result['ChargeTotalAmount'] == "") ? 0 : $result['ChargeTotalAmount']; + return $result; + } + + /** + * @brief get the customer info from XML + * @return array + */ + + /** + * + @code + + false + 64 + Conditional cash/payment discount | Korting contant | Escompte Conditionnel 2% + 4.00 + + S + 6.00 + + VAT + + + + @encode + */ + function get_allowance(): array + { + $result = []; + return $result; + } + /** + * @brief return the code of the document (cbc:"document"TypeCode) + * @return string "document string + cbc:"document"TypeCode" + */ + abstract function get_document_type_code(); + /** + * + * @return array + * @TODODNY + * Implémenter les allowances + */ + function get_info(): array + { + $result = []; + $result['id'] = $this->get_node_value('cbc:ID'); + $result['IssueDate'] = $this->get_node_value('cbc:IssueDate'); + $result['DueDate'] = $this->get_node_value('cbc:DueDate'); + $result['InvoiceTypeCode'] =$this->get_document_type_code(); + $result['DocumentCurrencyCode'] = $this->get_node_value('cbc:DocumentCurrencyCode'); + $result['BuyerReference'] = $this->get_node_value('cbc:BuyerReference'); + $result['ActualDeliveryDate'] = $this->get_node_value('//cac:Delivery[1]/cbc:ActualDeliveryDate[1]'); + /* + + 2018-07-01 + + */ + return $result; + } + + /** + * @brief make a PDF with the information from XMLInvoice + * @param \Database $cn + * @returns \PDF + */ + public function to_pdf(\Database $cn): \PDF + { + $pdf = new \PDF($cn); + $result = $this->get_info(); + + $pdf->setDossierInfo(_(" id ") . " " . $result['id']); + $pdf->AliasNbPages(); + $pdf->setAuthor("Noalyss"); + $pdf->AddPage(); + // 180 mm large + $pdf->setFont("DejaVu", "B", 12); + $pdf->write_cell(60, 4, _("Information facture")); + $pdf->line_new(10); + $pdf->setFont("DejaVu", "", 7); + $pdf->write(4, sprintf(_("Document ID %s"), $result['id'])); + $pdf->ln(); + $pdf->write(4, sprintf(_("Date facture %s"), $result['IssueDate'])); + $pdf->ln(); + $pdf->write(4, sprintf(_("Date échéance %s"), $result['DueDate'])); + $pdf->ln(); + $pdf->write(4, sprintf(_("Type et code document %s"), $result['InvoiceTypeCode'])); + $pdf->ln(); + $pdf->write(4, sprintf(_("Devise document %s"), $result['DocumentCurrencyCode'])); + $pdf->ln(); + $pdf->write(4, sprintf(_("Référence client %s"), $result['BuyerReference'])); + $pdf->ln(); + $pdf->write(4, sprintf(_("Date Livraison %s"), $result['ActualDeliveryDate'])); + $pdf->ln(10); + + $pdf->setFont("DejaVu", "B", 12); + $pdf->write_cell(60, 4, _("Fournisseur")); + $pdf->line_new(12); + $supplier = $this->get_supplier(); + $pdf->setFont("DejaVu", "", 7); + $pdf->write_cell(60, 4, $supplier['name']); + $pdf->write_cell(60, 4, $supplier['company_id']); + $pdf->write_cell(60, 4, $supplier['ID']); + $pdf->line_new(); + $pdf->write_cell(60, 4, $supplier['street']); + $pdf->write_cell(30, 4, $supplier['postcode']); + $pdf->write_cell(60, 4, $supplier['city']); + $pdf->write_cell(20, 4, $supplier['country_code']); + $pdf->line_new(10); + + $customer = $this->get_customer(); + $pdf->setFont("DejaVu", "B", 12); + $pdf->write_cell(60, 4, _("Client")); + $pdf->line_new(10); + $pdf->setFont("DejaVu", "", 7); + $pdf->write_cell(60, 4, $customer['name']); + $pdf->write_cell(60, 4, $customer['company_id']); + $pdf->write_cell(60, 4, $customer['ID']); + $pdf->line_new(); + $pdf->write_cell(60, 4, $customer['street']); + $pdf->write_cell(40, 4, $customer['postcode']); + $pdf->write_cell(60, 4, $customer['city']); + $pdf->write_cell(20, 4, $customer['country_code']); + $pdf->line_new(10); + + $pdf->setFont("DejaVu", "B", 12); + $pdf->write_cell(60, 4, _("Articles")); + $pdf->line_new(10); + $pdf->setFont("DejaVu", "", 7); + $result = $this->get_invoiceLine(); + $pdf->write_cell(50, 4, _("Code"), border: 'B'); + $pdf->write_cell(50, 4, _("Description"), border: 'B'); + $pdf->write_cell(30, 4, _("TVA"), border: 'B', align: 'R'); + $pdf->write_cell(25, 4, _("Quantité"), border: 'B', align: 'R'); + $pdf->write_cell(25, 4, _("Montant HT"), border: 'B', align: 'R'); + $pdf->line_new(); + + $nb_inline = count($result); + for ($i = 0; $i < $nb_inline; $i++) + { + $pdf->write_cell(50, 4, $result[$i]['name']); + $pdf->write_cell(50, 4, $result[$i]['description']); + $pdf->write_cell(25, 4, $result[$i]['tva_percent'], align: 'R'); + $pdf->write_cell(5, 4, $result[$i]['tva_id']); + $pdf->write_cell(25, 4, $result[$i]['quantity'], align: 'R'); + $pdf->write_cell(25, 4, $result[$i]['amount'], align: 'R'); + $pdf->line_new(); + } + $pdf->line_new(10); + + $pdf->setFont("DejaVu", "B", 12); + $pdf->write_cell(60, 4, _("Totaux")); + $pdf->line_new(10); + $pdf->setFont("DejaVu", "", 7); + $result = $this->get_amount_summary(); +// @TODO DNY : Qu'est-ce que LineExtension Amount ?? + $pdf->write_cell(50, 4, _("Base taxe")); + $pdf->write_cell(50, 4, $result['LineExtensionAmount']); + $pdf->line_new(); + $pdf->write_cell(50, 4, _("Total Hors Taxe")); + $pdf->write_cell(50, 4, $result['TaxExclusiveAmount']); + $pdf->line_new(); + $pdf->write_cell(50, 4, _("Total avec Taxe")); + $pdf->write_cell(50, 4, $result['TaxInclusiveAmount']); + $pdf->line_new(); + $pdf->write_cell(50, 4, _("Total à payer")); + $pdf->write_cell(50, 4, $result['PayableAmount']); + $pdf->line_new(); + $pdf->write_cell(50, 4, _("Total réduction")); + $pdf->write_cell(50, 4, $result['AllowanceTotalAmount']); + $pdf->line_new(); + $pdf->write_cell(50, 4, _("Total charge")); + $pdf->write_cell(50, 4, $result['ChargeTotalAmount']); + $pdf->line_new(); + + $pdf->line_new(10); + $pdf->setFont("DejaVu", "B", 12); + $pdf->write_cell(60, 4, _("TVA")); + $pdf->line_new(10); + $pdf->setFont("DejaVu", "", 7); + $result = $this->get_taxes(); + $pdf->write_cell(25, 4, _("% Taxe"), align: 'R', border: 'B'); + $pdf->write_cell(5, 4, _("Code taxe"), border: 'B'); + $pdf->write_cell(50, 4, _("Base"), align: 'R', border: 'B'); + $pdf->write_cell(50, 4, _("Taxe"), align: 'R', border: 'B'); + $pdf->line_new(); + $nb_inline = count($result); + for ($i = 0; $i < $nb_inline; $i++) + { + $pdf->write_cell(25, 4, $result[$i]['tax_percent'], align: 'R'); + $pdf->write_cell(5, 4, $result[$i]['tax_id']); + $pdf->write_cell(50, 4, $result[$i]['taxable_amount'], align: 'R'); + $pdf->write_cell(50, 4, $result[$i]['tax'], align: 'R'); + + $pdf->line_new(); + } + $pdf->line_new(10); + $pdf->setFont("DejaVu", "B", 12); + $pdf->write_cell(60, 4, _("Paiement")); + $pdf->line_new(10); + $pdf->setFont("DejaVu", "", 7); + $result = $this->get_payment_mean(); + $pdf->write_cell(50, 4, _("Code")); + $pdf->write_cell(50, 4, $result['payment_code']); + $pdf->line_new(); + $pdf->write_cell(50, 4, _("IBAN")); + $pdf->write_cell(50, 4, $result['iban']); + $pdf->line_new(); + $pdf->write_cell(50, 4, _("BIC")); + $pdf->write_cell(50, 4, $result['bic']); + $pdf->line_new(); + $pdf->write_cell(50, 4, _("Communication")); + $pdf->write_cell(50, 4, $result['label']); + $pdf->line_new(); + $nb_note = count($result['note']); + for ($i = 0; $i < $nb_note; $i++) + { + $pdf->write_cell(40, 4, _("note") . " " . $i); + $pdf->write_multi(140, 4, str_replace(["\n", "\r", "\t"], " ", $result['note'][$i])); + $pdf->line_new(); + } + /** + * display name of embedded from files + * @var $result(array of Document_Reference) + */ + $result=$this->get_embedded_document(); + if ( count($result) > 0) + { + $pdf->setFont("DejaVu", "B", 12); + $pdf->write_cell(50,4,_('Documents')); + $pdf->line_new(); + $pdf->setFont("DejaVu", "", 7); + $nb_result=count($result); + for ($i=0;$i< $nb_result;$i++) + { + $binary=$result[$i]->getBinary_object(); + $pdf->write_cell(50,4,$result[$i]->getId()); + $pdf->write_cell(50,4,$binary->filename); + $pdf->write_cell(50,4,$result[$i]->getDescription()); + $pdf->line_new(); + + } + + } + return $pdf; + } +} diff --git a/include/XMLDocument/xmlcreditnote.class.php b/include/XMLDocument/xmlcreditnote.class.php new file mode 100644 index 000000000..0ec62cdb1 --- /dev/null +++ b/include/XMLDocument/xmlcreditnote.class.php @@ -0,0 +1,31 @@ + string 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2' + 'ns2' => string 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2' + 'cac' => string 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2' + 'ns4' => string 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2' + ) + * + */ +class XMLCreditNote_Reader extends XML_Reader +{ + + public function __construct(\DOMDocument $domDocument) + { + parent::__construct($domDocument); + } + + /** + * @brief Build an XMLInvoice_Reader object from an XML string + * @param $string (string) XML + * @return \Noalyss\XMLDocument\XMLInvoice_Reader + * @throws \Exception if xml not valid + */ + static function build_from_string($string) + { + $dm = new \DOMDocument(); + if ($dm->loadXML($string) != false) + { + return new XMLCreditNote_Reader($dm); + } else + { + throw new \Exception("XC55: not a valid XML", 55); + } + } + + /** + * @brief Build an XMLInvoice_Reader object from an XML file + * @param $filename (string) file and path to the file + * @return \Noalyss\XMLDocument\XMLInvoice_Reader + * @throws \Exception if xml not valid + */ + static function build_from_file($filename) + { + if (!file_exists($filename)) + { + throw new \Exception("XC62: file not found $filename", 62); + } + $dm = new \DOMDocument(); + if ($dm->load($filename) != false) + { + return new XMLCreditNote_Reader($dm); + } else + { + throw new \Exception("XC55: not a valid XML", 55); + } + } + + /** + * @brief retrieve InvoiceLines + @code + + 2 + -3 + -1500 + + 123 + + + Description 2 + item name 2 + + 21382183120983 + + + NO + + + 09348023 + + + S + 25.0 + + VAT + + + + + 500 + + + @endcode + */ + function get_invoiceLine(): array + { + $result = []; + $node = $this->get_node("//ns3:CreditNoteLine"); + for ($e = 0; $e < $node->length; $e++) + { + $row = []; + $xml = simplexml_import_dom($node->item($e)); + $row ['quantity'] = $this->get_node_value("//cbc:CreditedQuantity", $e); + $row ['amount'] = $this->get_node_value("//cbc:LineExtensionAmount", $e); + $row ['description'] = $this->get_node_value("//cac:Item/cbc:Description", $e); + $row ['name'] = $this->get_node_value("//cac:CreditNoteLine/cac:Item/cbc:Name", $e); + $row ['unit_price'] = $this->get_node_value("//cac:CreditNoteLine/cac:Price/cbc:PriceAmount", $e); + $row ['tva_id'] = $this->get_node_value("//cac:CreditNoteLine/cac:Item/cac:ClassifiedTaxCategory/cbc:ID", $e); + $row ['tva_percent'] = $this->get_node_value("//cac:CreditNoteLine/cac:Item/cac:ClassifiedTaxCategory/cbc:Percent", $e); + + $result[] = $row; + } + return $result; + } + /** + * @brief return the code of the document + * @return string credit_node + code + */ + + function get_document_type_code() + { + return "credit note ".$this->get_node_value('cbc:CreditNoteTypeCode'); + } + /** + * @brief before executing xpath->query the namespace must be registered + * the NS are different for each type of doc + */ + + public function get_namespace() + { + + $a_namespace=array( + "cac"=> 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2' + ,"cbc"=> 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2' + ,"ns4"=>'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2' + ,"ns2"=>'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2' + ); + return $a_namespace; + } + /** + * @brief get the Taxes info from XML + * @return array + */ + function get_taxes(): array + { + $result = []; + $node = $this->get_node("//cac:TaxTotal/cac:TaxSubtotal"); + + for ($e = 0; $e < $node->length; $e++) + { + $row = []; + $xml = simplexml_import_dom($node->item($e)); + \Noalyss\Dbg::echo_var(0, htmlspecialchars($xml->asXML())); + // /Invoice/cac:TaxTotal[1]/cac:TaxSubtotal[1]/cbc:TaxableAmount[1] + $this->registerNS($xml); + + $row ['taxable_amount'] = $xml->xpath("//cbc:TaxableAmount")[$e] . ""; + $row ['tax'] = $xml->xpath("//cbc:TaxAmount")[$e] . ""; + $row ['tax_id'] = $xml->xpath("//cac:TaxCategory/cbc:ID")[$e] . ""; + $row ['tax_percent'] = $xml->xpath("//cac:TaxCategory/cbc:Percent")[$e] . ""; + $row ['name'] =$xml->xpath("//cac:CreditNoteLine/cac:Item/cbc:Name")[$e].""; + /** + * @TODODNY + * Implémenter les allowances + */ + $result[] = $row; + } + return $result; + } + +} diff --git a/include/XMLDocument/xmlinvoice_reader.class.php b/include/XMLDocument/xmlinvoice_reader.class.php index 47c96a81e..64cef8549 100644 --- a/include/XMLDocument/xmlinvoice_reader.class.php +++ b/include/XMLDocument/xmlinvoice_reader.class.php @@ -37,50 +37,23 @@ namespace Noalyss\XMLDocument; * Namespace standard (from XSD) * * Array -( - [xml] => http://www.w3.org/XML/1998/namespace - [cbc] => urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2 - [cac] => urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2 - [xmlns] => urn:oasis:names:specification:ubl:schema:xsd:Invoice-2 -) + ( + [xml] => http://www.w3.org/XML/1998/namespace + [cbc] => urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2 + [cac] => urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2 + [xmlns] => urn:oasis:names:specification:ubl:schema:xsd:Invoice-2 + ) * */ -class XMLInvoice_Reader +class XMLInvoice_Reader extends XML_Reader { - protected \DOMDocument $domDocument; - protected readonly \DOMXPath $xpath; - public function __construct(\DOMDocument $domDocument) { - $this->domDocument = $domDocument; - $this->xpath = new \DOMXPath($this->domDocument); - $this->xpath->registerNamespace("ns4", 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'); - $this->xpath->registerNamespace("cac", 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'); - $this->xpath->registerNamespace("cbc", 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'); + parent::__construct($domDocument); + } - - /** - * - * @return \DOMDocument - */ - public function get_domDocument(): \DOMDocument - { - return $this->domDocument; - } - - public function get_xpath(): \DOMXPath - { - return $this->xpath; - } - - public function set_domDocument($domDocument) - { - $this->domDocument = $domDocument; - return $this; - } - /** * @brief Build an XMLInvoice_Reader object from an XML string * @param $string (string) XML @@ -98,8 +71,7 @@ class XMLInvoice_Reader throw new \Exception("XR55: not a valid XML",55); } } - - /** + /** * @brief Build an XMLInvoice_Reader object from an XML file * @param $filename (string) file and path to the file * @return \Noalyss\XMLDocument\XMLInvoice_Reader @@ -120,150 +92,6 @@ class XMLInvoice_Reader throw new \Exception("XR55: not a valid XML", 55); } } - - /** - * @brief execute a XPATH query of the DOMDocument and return the result - * or null if nothing found - * @param $query (string) valid XPath Query - * @return null or DOMNodeList or DOMElement - */ - public function get_node($query) - { - if ($this->xpath->query($query)->length == 0) - { - return null; - } - return $this->xpath->query($query); - } - - /** - * @brief returns the embedded document in an array (keys : filecontent (BYTES),mimecode , filename) - * or false if there is no document - * @return bool|array of Document_Reference - * @throws \Exception if there are several documents - * - */ - public function get_embedded_document() - { - $a_document_reference=array(); - foreach (array( - "AdditionalDocumentReference", - "ReceiptDocumentReference", - "StatementDocumentReference", - "OriginatorDocumentReference", - "ContractDocumentReference" - ) - as $document) - { - $document=$this->domDocument->getElementsByTagName($document); - if ( $document->length == 0 ) { - continue; - } - for ($e=0;$e < $document->count();$e++) - { - $item=$document->item($e); - - if ( $item->nodeType == XML_ELEMENT_NODE ) - { - $id=$item->getElementsByTagName("ID")[0]?->nodeValue; - $description=$item->getElementsByTagName("DocumentDescription")[0]?->nodeValue; - $embedded_document=$item->getElementsByTagName("EmbeddedDocumentBinaryObject"); - if ( $embedded_document->length == 0) - { - continue; - } - $binary_object=new Binary_Object; - $binary_object->filecontent=base64_decode($embedded_document[0]->nodeValue); - $binary_object->mimecode=$embedded_document[0]->getAttribute("mimeCode"); - $binary_object->filename=$embedded_document[0]->getAttribute("filename"); - $document_reference=new Document_Reference(); - $document_reference->setId($id) - ->setDescription($description) - ->setBinary_object($binary_object); - $a_document_reference[]=clone $document_reference; - } - } - - } - return $a_document_reference; - } - - /** - * @brief get the node value - * @param $query (string) valid XPath query - * @param $ix (int) the item number - * @return string - */ - function get_node_value($query, $ix = 0) - { - return $this->get_node($query)?->item($ix)?->nodeValue; - } - - /** - * @brief get the customer info from XML - * @return array - */ - function get_customer(): array - { - $result = []; - $result['ID'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cbc:EndpointID[1]"); - $result['name'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PartyName[1]/cbc:Name[1]'); - $result['street'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:StreetName[1]'); - $result['city'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:CityName[1]'); - $result['postcode'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:PostalZone[1]'); - $result['country_code'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cac:Country[1]/cbc:IdentificationCode[1]"); - $result['company_id'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PartyTaxScheme[1]/cbc:CompanyID[1]"); - $result['scheme']=$this->get_node("//cac:AccountingCustomerParty[1]/cac:Party[1]/cbc:EndpointID[1]")[0]->getAttribute("schemeID"); - return $result; - } - - /** - * @brief get the supplier info from XML - * @return array - */ - function get_supplier(): array - { - $result = []; - $result['ID'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cbc:EndpointID[1]"); - $result['name'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PartyName[1]/cbc:Name[1]"); - $result['street'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:StreetName[1]"); - $result['city'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:CityName[1]"); - $result['postcode'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:PostalZone[1]"); - $result['country_code'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cac:Country[1]/cbc:IdentificationCode[1]"); - $result['company_id'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PartyTaxScheme[1]/cbc:CompanyID[1]"); - $result['scheme']=$this->get_node("//cac:AccountingSupplierParty[1]/cac:Party[1]/cbc:EndpointID[1]")[0]->getAttribute("schemeID"); - return $result; - } - - /** - * @brief get the Taxes info from XML - * @return array - */ - function get_taxes(): array - { - $result = []; - $node = $this->get_node("//cac:TaxTotal/cac:TaxSubtotal"); - - for ($e = 0; $e < $node->length; $e++) - { - $row = []; - $xml = simplexml_import_dom($node->item($e)); - // /Invoice/cac:TaxTotal[1]/cac:TaxSubtotal[1]/cbc:TaxableAmount[1] - - $row ['taxable_amount'] = $xml->xpath("//cbc:TaxableAmount")[$e] . ""; - $row ['tax'] = $xml->xpath("//cbc:TaxAmount")[$e] . ""; - $row ['tax_id'] = $xml->xpath("//cac:TaxCategory/cbc:ID")[$e] . ""; - $row ['tax_percent'] = $xml->xpath("//cac:TaxCategory/cbc:Percent")[$e] . ""; - // $row ['name'] =$xml->xpath("//cac:InvoiceLine/cac:Item/cbc:Name")[$e]."
"; - /** - * @TODODNY - * Implémenter les allowances - */ - $result[] = $row; - } - return $result; - } - /** * @brief retrieve InvoiceLines * @TODO XMLInvoice_Reader->get_invoiceLine * Implémenter les allowances @@ -288,286 +116,58 @@ class XMLInvoice_Reader } return $result; } - /** - * @brief get the Payment Means info from XML - * @return array + * @brief return the code of the document + * @return string invoice + code */ - function get_payment_mean(): array + function get_document_type_code() { - $result = []; - $result['payment_code'] = $this->get_node_value("//cac:PaymentMeans[1]/cbc:PaymentMeansCode[1]"); - $result['label'] = $this->get_node_value("//cac:PaymentMeans[1]/cbc:PaymentID[1]"); - $result['iban'] = $this->get_node_value("//cac:PaymentMeans[1]/cac:PayeeFinancialAccount[1]/cbc:ID[1]"); - $result['bic'] = $this->get_node_value("//cac:PaymentMeans[1]/cac:PayeeFinancialAccount[1]/cac:FinancialInstitutionBranch[1]/cbc:ID[1]"); - $result['note'] = []; - $note = $this->get_node("//cac:PaymentTerms/cbc:Note"); - if ($note != null) - { - $a = $note->length; - for ($i = 0; $i < $a; $i++) - { - $result['note'][] = $note->item(0)->nodeValue; - } - } - /** - - In geval van betaling binnen 14 dagen is 2% (52.00€) betalingskorting van - toepassing en het te betalen bedrag = 3053.68€ - En cas de paiement dans les 14 jours, l'escompte conditionnel de 2% (52.00€) est appliqué et le montant payable = 3053.68€ - In case of payment within 14 days, 2% (52.00€) conditional cash/payment discount applies and the payable amount = 3053.68€ - - - */ - return $result; + return "invoice ".$this->get_node_value('cbc:InvoiceTypeCode'); } - /** - * @brief get the node value from simpleXML - * @param $xml (\SimpleXMLElement) fragment of XML dom - * @param $query (string) XPath query - * @return string + * @brief before executing xpath->query the namespace must be registered + * the NS are different for each type of doc + * @param $xml (null or simpleXML) */ - protected function get_simple_xml_value(\SimpleXMLElement $xml, $query): string + + public function get_namespace() { - $x = $xml->xpath($query); - $result = (count($x) == 0) ? "" : $x[0]; - return (string) $result; - } - - /** - * @brief get the amount summary info from XML - * @return array - */ - function get_amount_summary(): array - { - $result = []; - $node = $this->get_node("//cac:LegalMonetaryTotal"); - $xml = simplexml_import_dom($node->item(0)); - $result['LineExtensionAmount'] = $this->get_simple_xml_value($xml, "cbc:LineExtensionAmount"); - $result['TaxExclusiveAmount'] = $this->get_simple_xml_value($xml, "cbc:TaxExclusiveAmount"); - $result['TaxInclusiveAmount'] = $this->get_simple_xml_value($xml, "cbc:TaxInclusiveAmount"); - $result['PayableAmount'] = $this->get_simple_xml_value($xml, "cbc:PayableAmount"); - $result['AllowanceTotalAmount'] = $this->get_simple_xml_value($xml, "cbc:AllowanceTotalAmount"); - $result['AllowanceTotalAmount'] = ($result['AllowanceTotalAmount'] == "") ? 0 : $result['AllowanceTotalAmount']; - $result['ChargeTotalAmount'] = $this->get_simple_xml_value($xml, "cbc:ChargeTotalAmount"); - $result['ChargeTotalAmount'] = ($result['ChargeTotalAmount'] == "") ? 0 : $result['ChargeTotalAmount']; - return $result; - } - - /** - * @brief get the customer info from XML - * @return array - */ - - /** - * - @code - - false - 64 - Conditional cash/payment discount | Korting contant | Escompte Conditionnel 2% - 4.00 - - S - 6.00 - - VAT - - - - @encode - */ - function get_allowance(): array - { - $result = []; - return $result; - } - - /** - * - * @return array - * @TODODNY - * Implémenter les allowances - */ - function get_info(): array - { - $result = []; - $result['id'] = $this->get_node_value('cbc:ID'); - $result['IssueDate'] = $this->get_node_value('cbc:IssueDate'); - $result['DueDate'] = $this->get_node_value('cbc:DueDate'); - $result['InvoiceTypeCode'] = $this->get_node_value('cbc:InvoiceTypeCode'); - $result['DocumentCurrencyCode'] = $this->get_node_value('cbc:DocumentCurrencyCode'); - $result['BuyerReference'] = $this->get_node_value('cbc:BuyerReference'); - $result['ActualDeliveryDate'] = $this->get_node_value('//cac:Delivery[1]/cbc:ActualDeliveryDate[1]'); - /* - - 2018-07-01 - - */ - return $result; - } - - /** - * @brief make a PDF with the information from XMLInvoice - * @param \Database $cn - * @returns \PDF - */ - public function to_pdf(\Database $cn): \PDF - { - $pdf = new \PDF($cn); - $result = $this->get_info(); + $a_namespace=array( + "cac"=>'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2' + ,"cbc"=>'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2' + ,"ns4"=>'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2' + ); + return $a_namespace; - $pdf->setDossierInfo(_(" id ")." ".$result['id']); - $pdf->AliasNbPages(); - $pdf->setAuthor("Noalyss"); - $pdf->AddPage(); - // 180 mm large - $pdf->setFont("DejaVu", "B", 12); - $pdf->write_cell(60, 4, _("Information facture")); - $pdf->line_new(10); - $pdf->setFont("DejaVu", "", 7); - $pdf->write(4, sprintf(_("Document ID %s"), $result['id'])); - $pdf->ln(); - $pdf->write(4, sprintf(_("Date facture %s"), $result['IssueDate'])); - $pdf->ln(); - $pdf->write(4, sprintf(_("Date échéance %s"), $result['DueDate'])); - $pdf->ln(); - $pdf->write(4, sprintf(_("Code facture %s"), $result['InvoiceTypeCode'])); - $pdf->ln(); - $pdf->write(4, sprintf(_("Devise docuemnt %s"), $result['DocumentCurrencyCode'])); - $pdf->ln(); - $pdf->write(4, sprintf(_("Référence client %s"), $result['BuyerReference'])); - $pdf->ln(); - $pdf->write(4, sprintf(_("Date Livraison %s"), $result['ActualDeliveryDate'])); - $pdf->ln(10); + } + /** + * @brief get the Taxes info from XML + * @return array + */ + function get_taxes(): array + { + $result = []; + $node = $this->get_node("//cac:TaxTotal/cac:TaxSubtotal"); - $pdf->setFont("DejaVu", "B", 12); - $pdf->write_cell(60, 4, _("Fournisseur")); - $pdf->line_new(12); - $supplier = $this->get_supplier(); - $pdf->setFont("DejaVu", "", 7); - $pdf->write_cell(60, 4, $supplier['name']); - $pdf->write_cell(60, 4, $supplier['company_id']); - $pdf->write_cell(60, 4, $supplier['ID']); - $pdf->line_new(); - $pdf->write_cell(60, 4, $supplier['street']); - $pdf->write_cell(30, 4, $supplier['postcode']); - $pdf->write_cell(60, 4, $supplier['city']); - $pdf->write_cell(20, 4, $supplier['country_code']); - $pdf->line_new(10); - - $customer = $this->get_customer(); - $pdf->setFont("DejaVu", "B", 12); - $pdf->write_cell(60, 4, _("Client")); - $pdf->line_new(10); - $pdf->setFont("DejaVu", "", 7); - $pdf->write_cell(60, 4, $customer['name']); - $pdf->write_cell(60, 4, $customer['company_id']); - $pdf->write_cell(60, 4, $customer['ID']); - $pdf->line_new(); - $pdf->write_cell(60, 4, $customer['street']); - $pdf->write_cell(40, 4, $customer['postcode']); - $pdf->write_cell(60, 4, $customer['city']); - $pdf->write_cell(20, 4, $customer['country_code']); - $pdf->line_new(10); - - $pdf->setFont("DejaVu", "B", 12); - $pdf->write_cell(60, 4, _("Articles")); - $pdf->line_new(10); - $pdf->setFont("DejaVu", "", 7); - $result = $this->get_invoiceLine(); - $pdf->write_cell(50, 4, _("Code"), border: 'B'); - $pdf->write_cell(50, 4, _("Description"), border: 'B'); - $pdf->write_cell(30, 4, _("TVA"), border: 'B', align: 'R'); - $pdf->write_cell(25, 4, _("Quantité"), border: 'B', align: 'R'); - $pdf->write_cell(25, 4, _("Montant HT"), border: 'B', align: 'R'); - $pdf->line_new(); - - $nb_inline = count($result); - for ($i = 0; $i < $nb_inline; $i++) + for ($e = 0; $e < $node->length; $e++) { - $pdf->write_cell(50, 4, $result[$i]['name']); - $pdf->write_cell(50, 4, $result[$i]['description']); - $pdf->write_cell(25, 4, $result[$i]['tva_percent'], align: 'R'); - $pdf->write_cell(5, 4, $result[$i]['tva_id']); - $pdf->write_cell(25, 4, $result[$i]['quantity'], align: 'R'); - $pdf->write_cell(25, 4, $result[$i]['amount'], align: 'R'); - $pdf->line_new(); - } - $pdf->line_new(10); + $row = []; + $xml = simplexml_import_dom($node->item($e)); + \Noalyss\Dbg::echo_var(0, htmlspecialchars($xml->asXML())); + // /Invoice/cac:TaxTotal[1]/cac:TaxSubtotal[1]/cbc:TaxableAmount[1] + $this->registerNS($xml); - $pdf->setFont("DejaVu", "B", 12); - $pdf->write_cell(60, 4, _("Totaux")); - $pdf->line_new(10); - $pdf->setFont("DejaVu", "", 7); - $result = $this->get_amount_summary(); -// @TODO DNY : Qu'est-ce que LineExtension Amount ?? - $pdf->write_cell(50, 4, _("Base taxe")); - $pdf->write_cell(50, 4, $result['LineExtensionAmount']); - $pdf->line_new(); - $pdf->write_cell(50, 4, _("Total Hors Taxe")); - $pdf->write_cell(50, 4, $result['TaxExclusiveAmount']); - $pdf->line_new(); - $pdf->write_cell(50, 4, _("Total avec Taxe")); - $pdf->write_cell(50, 4, $result['TaxInclusiveAmount']); - $pdf->line_new(); - $pdf->write_cell(50, 4, _("Total à payer")); - $pdf->write_cell(50, 4, $result['PayableAmount']); - $pdf->line_new(); - $pdf->write_cell(50, 4, _("Total réduction")); - $pdf->write_cell(50, 4, $result['AllowanceTotalAmount']); - $pdf->line_new(); - $pdf->write_cell(50, 4, _("Total charge")); - $pdf->write_cell(50, 4, $result['ChargeTotalAmount']); - $pdf->line_new(); - - $pdf->line_new(10); - $pdf->setFont("DejaVu", "B", 12); - $pdf->write_cell(60, 4, _("TVA")); - $pdf->line_new(10); - $pdf->setFont("DejaVu", "", 7); - $result = $this->get_taxes(); - $pdf->write_cell(25, 4, _("% Taxe"), align: 'R', border: 'B'); - $pdf->write_cell(5, 4, _("Code taxe"), border: 'B'); - $pdf->write_cell(50, 4, _("Base"), align: 'R', border: 'B'); - $pdf->write_cell(50, 4, _("Taxe"), align: 'R', border: 'B'); - $pdf->line_new(); - $nb_inline = count($result); - for ($i = 0; $i < $nb_inline; $i++) - { - $pdf->write_cell(25, 4, $result[$i]['tax_percent'], align: 'R'); - $pdf->write_cell(5, 4, $result[$i]['tax_id']); - $pdf->write_cell(50, 4, $result[$i]['taxable_amount'], align: 'R'); - $pdf->write_cell(50, 4, $result[$i]['tax'], align: 'R'); - - $pdf->line_new(); + $row ['taxable_amount'] = $xml->xpath("//cbc:TaxableAmount")[$e] . ""; + $row ['tax'] = $xml->xpath("//cbc:TaxAmount")[$e] . ""; + $row ['tax_id'] = $xml->xpath("//cac:TaxCategory/cbc:ID")[$e] . ""; + $row ['tax_percent'] = $xml->xpath("//cac:TaxCategory/cbc:Percent")[$e] . ""; + $row ['name'] =$xml->xpath("//cac:InvoiceLine/cac:Item/cbc:Name")[$e].""; + /** + * @TODODNY + * Implémenter les allowances + */ + $result[] = $row; } - $pdf->line_new(10); - $pdf->setFont("DejaVu", "B", 12); - $pdf->write_cell(60, 4, _("Paiement")); - $pdf->line_new(10); - $pdf->setFont("DejaVu", "", 7); - $result = $this->get_payment_mean(); - $pdf->write_cell(50, 4, _("Code")); - $pdf->write_cell(50, 4, $result['payment_code']); - $pdf->line_new(); - $pdf->write_cell(50, 4, _("IBAN")); - $pdf->write_cell(50, 4, $result['iban']); - $pdf->line_new(); - $pdf->write_cell(50, 4, _("BIC")); - $pdf->write_cell(50, 4, $result['bic']); - $pdf->line_new(); - $pdf->write_cell(50, 4, _("Communication")); - $pdf->write_cell(50, 4, $result['label']); - $pdf->line_new(); - $nb_note = count($result['note']); - for ($i = 0; $i < $nb_note; $i++) - { - $pdf->write_cell(40, 4, _("note") . " " . $i); - $pdf->write_multi(140, 4, str_replace(["\n","\r","\t"]," ",$result['note'][$i])); - $pdf->line_new(); - } - return $pdf; + return $result; } } diff --git a/include/constant.php b/include/constant.php index 0f37c02b8..b6c622733 100644 --- a/include/constant.php +++ b/include/constant.php @@ -443,7 +443,9 @@ function noalyss_class_autoloader($class) 'noalyss\smtpmail'=>'lib/smtpmail.class.php', 'noalyss\iban_number'=>'lib/iban_number.class.php', 'noalyss\xmldocument\document_reference'=>'XMLDocument/document_reference_type.class.php', - 'noalyss\xmldocument\binary_object'=>'XMLDocument/document_reference_type.class.php' + 'noalyss\xmldocument\binary_object'=>'XMLDocument/document_reference_type.class.php', + 'noalyss\xmldocument\xml_reader'=>'XMLDocument/xml_reader.class.php', + "noalyss\xmldocument\xmlcreditnote_reader"=>"XMLDocument/xmlcreditnote_reader.class.php" ); if (isset ($aClass[$class])) { require_once NOALYSS_INCLUDE . "/" . $aClass[$class];