E-INVOICE extract all documents from XML and store in DB

This commit is contained in:
sparkyx 2025-11-07 18:22:35 +01:00
parent 9db299594d
commit 35d73582b0
7 changed files with 288 additions and 96 deletions

View file

@ -0,0 +1,90 @@
<?php
namespace Noalyss\XMLDocument;
/*
* This file is part of NOALYSS.
*
* NOALYSS is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* NOALYSS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NOALYSS; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu 22/10/23
/**
* @file
* @brief Document Reference Type ; documents included in a UBL21
* xml
*/
class Binary_Object
{
public $filecontent;
public $mimecode;
public $filename;
function __toString(): string
{
return "Binary_Object[filecontent=" . $this->filecontent
. ", mimecode=" . $this->mimecode
. ", filename=" . $this->filename
. "]";
}
}
class Document_Reference
{
protected $id;
protected $description;
protected Binary_Object $binary_object;
function __toString(): string
{
return "Document_Reference[id=" . $this->id
. ", description=" . $this->description
. ", binary_object=" . $this->binary_object
. "]";
}
public function getId()
{
return $this->id;
}
public function getDescription()
{
return $this->description;
}
public function getBinary_object(): Binary_Object
{
return $this->binary_object;
}
public function setId($id)
{
$this->id = $id;
return $this;
}
public function setDescription($description)
{
$this->description = $description;
return $this;
}
public function setBinary_object(Binary_Object $binary_object)
{
$this->binary_object = $binary_object;
return $this;
}
}

View file

@ -86,6 +86,14 @@ class InvoiceUBL21 extends XMLInvoice {
/**
* check that PEPPOL ID is valid
*/
if ( isset($company['COMPANY_UBL_ID']))
{
if ( strpos($company['COMPANY_UBL_ID'],':') == 0 )
{
$a_error[]="COMPANY_UBL_ID";
}
}
return $a_error;
}
/**
@ -107,7 +115,7 @@ class InvoiceUBL21 extends XMLInvoice {
$a_error[]=$value;
}
}
if ( $this->data['customer'][ATTR_DEF_PEPPOLID] != "")
if ( $this->data['customer']["endpoint_id"] != "")
{
// check if peppol id has the form 9999:9999...
list($scheme_id,$peppol)=explode(":", $this->data['customer'][$value]);

View file

@ -56,6 +56,9 @@ class XMLInvoice_Reader
{
$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');
}
/**
@ -136,35 +139,53 @@ class XMLInvoice_Reader
/**
* @brief returns the embedded document in an array (keys : filecontent (BYTES),mimecode , filename)
* or false if there is no document
* @return bool|array keys : filecontent (BYTES),mimecode , filename)
* or false if there is no document
* @return bool|array of array : filecontent (BYTES),mimecode , filename)
* @throws \Exception if there are several documents
*
*/
public function get_embedded_document()
{
if (($node = $this->get_node("//cac:AdditionalDocumentReference")) == false)
{
return false;
}
if ($node->length > 1)
{
throw new \Exception("XR110 Too many documents found", 110);
}
///@var DOMNodeList $data //cac:AdditionalDocumentReference[1]/cac:Attachment[1]/cbc:EmbeddedDocumentBinaryObject[1]
if (
($data = $this->get_node("//cac:AdditionalDocumentReference[1]/cac:Attachment[1]/cbc:EmbeddedDocumentBinaryObject[1]")) == null
$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;
}
}
throw new \Exception("X116 Document corrupted", 116);
}
$filecontent = base64_decode($data->item(0)->nodeValue);
$mimecode = $data->item(0)->getAttribute("mimeCode");
$filename = $data->item(0)->getAttribute("filename");
return array("filecontent" => $filecontent,
"mimecode" => $mimecode,
"filename" => $filename);
return $a_document_reference;
}
/**
@ -192,6 +213,7 @@ class XMLInvoice_Reader
$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;
}
@ -209,6 +231,7 @@ class XMLInvoice_Reader
$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;
}

View file

@ -320,70 +320,55 @@ class Acc_Document extends Document {
//,mimecode mimetype and filename (string)
try
{
$embedded_file=$xmlreader->get_embedded_document();
if ($embedded_file == false)
{
$embedded_file=array();
// create a PDF with standard information
$pdf=$xmlreader->to_pdf($this->db);
$file_oid=$this->db->lo_write($pdf->Output("S"));
$embedded_file['filename']="invoice.pdf";
$embedded_file['mimecode']="application/pdf";
}
else
{
$file_oid=$this->db->lo_write($embedded_file['filecontent']);
if ( $file_oid == false )
{
// create a PDF with standard information
$pdf=$xmlreader->to_pdf($this->db);
$file_oid=$this->db->lo_write($pdf->Output("S"));
}
}
// create a PDF with standard information
$pdf=$xmlreader->to_pdf($this->db);
$file_oid=$this->db->lo_write($pdf->Output("S"));
//@var $file_oid OID of the large object saved in DB
$this->d_name="invoice.pdf";
$this->d_description="Auto generated invoice";
$this->d_lob=$file_oid;
$this->d_mimetype="application/pdf";
// save extracted document into DB
$this->db->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2,
jr_pj_type=$3 where jr_id=$4",
array(
$this->d_lob
, $this->d_name
, $this->d_description
, $this->d_id
)
);
// save all the documents into the DB
// @var embedded_file (array of Noalyss\XML\Document_Reference)
$embedded_file=$xmlreader->get_embedded_document();
$nb_file = count($embedded_file);
for ($i=0;$i <$nb_file ; $i++)
{
// other documents save in jrn_sup_document
// @var $file (Binary File from XML)
$binary=$embedded_file[$i]->getBinary_object();
//@var $sup_oid : oid of the supplemental
$sup_oid=$this->db->lo_write($binary->filecontent);
$jrn_sup_document=new Jrn_Sup_Document_SQL($this->db);
$jrn_sup_document->jr_id=$this->d_id;
$jrn_sup_document->js_mimetype=$binary->mimecode;
$jrn_sup_document->js_filename=$binary->filename;
$jrn_sup_document->js_lob=$sup_oid;
$jrn_sup_document->js_description=$embedded_file[$i]->getDescription();
$jrn_sup_document->js_cbc_id=$embedded_file[$i]->getId();
$jrn_sup_document->save();
}
$this->d_name=$embedded_file['filename'];
$this->d_description=$embedded_file['filename'];
$this->d_lob=$file_oid;
$this->d_mimetype=$embedded_file['mimecode'];
// save extracted document into DB
$this->db->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2,
jr_pj_type=$3 where jr_id=$4",
array(
$this->d_lob
, $this->d_name
, $this->d_description
, $this->d_id
)
);
return $file_oid;
} catch (\Exception $e ) {
\record_log($e);
// if exception is not too many document or document corrupted
// then rethrow the exception
if ( !in_array(e->getCode(),[110,116]) )
{
throw new \Exception("X281 ",281,$e);
}
throw new \Exception("X281 ",281,$e);
}
}
// save new document
$this->db->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2,
jr_pj_type=$3 where jr_id=$4",
array(
$oid
, $_FILES['pj']['name']
, $_FILES['pj']['type']
, $this->d_id
)
);
$this->d_name=$_FILES['pj']['name'];
$this->d_description=$_FILES['pj']['name'];
$this->d_lob=$oid;
$this->d_mimetype=$_FILES['pj']['type'];
$this->db->commit();
return $oid;

View file

@ -441,7 +441,9 @@ function noalyss_class_autoloader($class)
'noalyss\xmldocument\xmlinvoice_reader'=>'XMLDocument/xmlinvoice_reader.class.php',
'noalyss\mail_parameter'=>'lib/mail_parameter.class.php',
'noalyss\smtpmail'=>'lib/smtpmail.class.php',
'noalyss\iban_number'=>'lib/iban_number.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'
);
if (isset ($aClass[$class])) {
require_once NOALYSS_INCLUDE . "/" . $aClass[$class];

View file

@ -0,0 +1,71 @@
<?php
/**
* Autogenerated file
* This file is part of NOALYSS.
*
* NOALYSS is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* NOALYSS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NOALYSS; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
require_once NOALYSS_INCLUDE . '/lib/data_sql.class.php';
require_once NOALYSS_INCLUDE . '/class/database.class.php';
/**
* @file
* @brief abstract of the table public.jrn_sup_document
*/
/**
* @class Jrn_Sup_Document_SQL
* @brief abstract of the table public.jrn_sup_document
*/
class Jrn_Sup_Document_SQL extends \Table_Data_SQL
{
function __construct(Database $p_cn, $p_id = -1)
{
$this->table = "public.jrn_sup_document";
$this->primary_key = "js_id";
/*
* List of columns
*/
$this->name = array(
"js_id" => "js_id"
, "js_mimetype" => "js_mimetype"
, "js_lob" => "js_lob"
, "jr_id" => "jr_id"
,'js_filename'=>'js_filename'
,'js_description'=>'js_description'
,'js_cbc_id'=>'js_cbc_id'
);
/*
* Type of columns
*/
$this->type = array(
"js_id" => "numeric"
, "js_mimetype" => "text"
, "js_lob" => "oid"
, "jr_id" => "numeric"
,'js_filename'=>'text'
,'js_description'=>'text'
,'js_cbc_id'=>'text'
);
$this->default = array("js_id" => "auto");
$this->date_format = "DD.MM.YYYY";
parent::__construct($p_cn, $p_id);
}
}

View file

@ -233,7 +233,7 @@ insert into parameter_extra(pe_code,pe_label) values ('COMPANY_LEGAL_REGISTRATIO
insert into parameter_extra(pe_code,pe_label) values ('COMPANY_LEGAL_ENTITY','Forme légal de la société (SRL,ASBL,AISBL,...') on conflict do nothing;
insert into parameter_extra(pe_code,pe_label) values ('COMPANY_BANK_IBAN','Compte en banque (IBAN)') on conflict do nothing;
insert into parameter_extra(pe_code,pe_label) values ('COMPANY_BANK_BIC','BIC Bank Identification Code') on conflict do nothing;
insert into parameter_extra(pe_code,pe_label) values ('COMPANY_UBL_ID','ID PEPPOL') on conflict do nothing;
insert into parameter_extra(pe_code,pe_label) values ('COMPANY_UBL_ID','Identifiant PEPPOL') on conflict do nothing;
insert into parameter_extra(pe_code,pe_label) values ('SIREN','n° SIREN') on conflict do nothing;
insert into parameter_extra(pe_code,pe_label) values ('SIRET','n° SIRET') on conflict do nothing;
@ -438,24 +438,14 @@ insert into jnt_fic_attr (fd_id,ad_id,jnt_order)
--
CREATE OR REPLACE FUNCTION comptaproc.update_peppol ()
RETURNS int2
/*- SETOF menu_tree : (TABLE FUNCTION) menu_tree is a type of a rowtype create type menu_tree as (code text,description text);
- int2, text,varchar...
- trigger : if called from a trigger
- void : nothing
- boolean
*/ AS
AS
$BODY$
declare
/*
* Section for variables
*/
n_card_id int;
vat_number text;
record_card RECORD;
result_fct int;
country_code text;
n_card_id int;
vat_number text;
record_card RECORD;
result_fct int;
begin
select pi_value into country_code from parameter_internal where pi_id ='COUNTRY_CODE';
result_fct := 0;
@ -478,4 +468,27 @@ end;
$BODY$
LANGUAGE plpgsql;
select comptaproc.update_peppol();
select comptaproc.update_peppol();
CREATE TABLE public.jrn_sup_document (
js_id int8 GENERATED BY DEFAULT AS IDENTITY( INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1 NO CYCLE) NOT NULL, -- PK
js_mimetype text NOT NULL, -- Mimetype
js_lob oid NOT NULL, -- OID
jr_id int8 NOT NULL, -- FK to jrn
js_filename text not null,
js_description text ,
js_cbc_id text,
CONSTRAINT jrn_sup_document_pkey PRIMARY KEY (js_id),
CONSTRAINT jrn_sup_document_jrn_fk FOREIGN KEY (jr_id) REFERENCES public.jrn(jr_id) ON DELETE SET NULL ON UPDATE CASCADE
);
COMMENT ON TABLE public.jrn_sup_document IS 'Supplement document for operation';
-- Column comments
COMMENT ON COLUMN public.jrn_sup_document.js_id IS 'PK';
COMMENT ON COLUMN public.jrn_sup_document.js_mimetype IS 'Mimetype';
COMMENT ON COLUMN public.jrn_sup_document.js_lob IS 'OID';
COMMENT ON COLUMN public.jrn_sup_document.jr_id IS 'FK to jrn';
COMMENT ON COLUMN public.jrn_sup_document.js_description IS 'Description of document';
COMMENT ON COLUMN public.jrn_sup_document.js_cbc_id IS 'ID in XML';