25.822 [issue_date] => 2025-06-10 [due_date] => [supplier] => Array ( [name] => My company sprl [street] => Allée des Zoulons [postalzone] => 1080 [city] => Molenbeek Saint Jean [country] => BE [supplier_vat_id] => BE012345678 [registration_name] => My Company sprl ) [customer] => Array ( [name] => This asbl [street] => [postalzone] => [city] => [country] => [customer_vat_id] => numéro TVA [registration_name] => This asbl ) [currency] => 0 [info] => Array [order] = order reference [communication] = communication added to the invoice [operation] => Array ( [0] => Array ( [card_id] => 568 [quantity] => 1.0000 [price] => 10.0000 [vat] => 2.1000 [vat_id] => 1 [vat_reversed] => 0.0000 [code_quantity]=> EA [vat_code]=> Code VAT for PEPPOL (S,K,...) ) [1] => Array ( [card_id] => 164 [quantity] => 5.0000 [price] => 83.4500 [vat] => 17.5200 [vat_id] => 1 [vat_reversed] => 0.0000 [code_quantity]=> EA [vat_code]=> Code VAT for PEPPOL (S,K,...) ) [2] => Array ( [card_id] => 483 [quantity] => 1.0000 [price] => 72.0000 [vat] => 15.1200 [vat_id] => 5 [vat_reversed] => 15.1200 [code_quantity]=> EA [vat_code]=> Code VAT for PEPPOL (S,K,...) ) ) * * ) * @endcode * */ abstract class XMLInvoice extends \DOMDocument { protected $cn; //!< Database conx , current folder protected $data; //! $data (Array) data retrieve from DB protected $jr_id; //! $jr_id (int) is JRN.JR_ID function __construct(\Database $conx) { parent::__construct("1.0", "UTF-8"); $this->cn=$conx; $this->data=[]; } /** * @brief return data * @return array */ public function get_data() { return $this->data; } /** * @brief returns data * @param $data (array) * @return XMLInvoice */ public function set_data($data): XMLInvoice { $this->data = $data; return $this; } /** * @brief get Database Connexion * @param $cn (\Database) * @return XMLInvoice */ public function get_db_conx():\Database { return $this->cn; } /** * @brief set Database Connexion * @param $cn (\Database) * @return XMLInvoice */ public function set_db_conx(\Database $cn): XMLInvoice { $this->cn = $cn; return $this; } /** * @brief transform an operation ($jr_id) into an array, which contains * needed information for making an e-invoice * @param $jr_id (int) operation JRN.JR_ID * @return array with all info7 * */ function build_data($jr_id):array { global $g_parameter; $this->jr_id=$jr_id; $operation = new \Acc_Sold($this->cn,$jr_id); $operation->get(); $result=array(); $result["id"]= $operation->det->jr_pj_number; $result["issue_date"]=$operation->det->jr_date; $result["due_date"]=($operation->det->jr_ech=="")?$operation->det->jr_date:$operation->det->jr_ech; // supplier $result['supplier']=$this->fill_supplier(); //customer $result['customer']=$this->fill_customer($operation->det->array[0]['qs_client']); // currency $result['currency']=$this->cn->get_value("select cr_code_iso from currency where id=$1" ,array($operation->det->currency_id)); // document description $result['description']=$operation->det->jr_comment; // note if any $result['note']= $result['description']; $a_note = $this->cn->get_array("select n_id,n_text from jrn_note where jr_id=$1",[$operation->det->jr_id]); $nb_note=count($a_note); for ($e=0;$e<$nb_note;$e++) { $result['note'].= html_entity_decode($a_note[$e]['n_text']??""); } // goods and services $result['operation']=array(); $nb_operation= count($operation->det->array); for ($i=0;$i < $nb_operation;$i++) { $result['operation'][$i]['card_id']=$operation->det->array[$i]['qs_fiche']; $result['operation'][$i]['quantity']=$operation->det->array[$i]['qs_quantite']; $card=new \Fiche($this->cn,$operation->det->array[$i]['qs_fiche']); $result['operation'][$i]['qcode']=$card->get_attribute(ATTR_DEF_QUICKCODE); $result['operation'][$i]['name']=$card->get_attribute(ATTR_DEF_NAME); $result['operation'][$i]['description']=$operation->det->array[$i]['j_text']; if ($operation->det->array[$i]['j_text'] == "") { $a=$card->get_attribute(9); if ( $a != "") { $result['operation'][$i]['description']=$a; }else { $result['operation'][$i]['description']=$result['operation'][$i]['name']; } } // get the type of unity, if not found then it will be EA $x= $card->get_attribute(ATTR_DEF_QUANTITY_TYPE,0); $result['operation'][$i]['code_quantity']=($x===false||$x=="")?"EA":$x; // $operation->det->currency_id == 0 default currency of the folder if ($operation->det->currency_id == 0 ) { $result['operation'][$i]['price']=$operation->det->array[$i]['qs_price']; $result['operation'][$i]['price_unit']=$operation->det->array[$i]['qs_unit']; $result['operation'][$i]['vat']=$operation->det->array[$i]['qs_vat']; } else { $result['operation'][$i]['price']=$operation->det->array[$i]['oc_amount']; $result['operation'][$i]['price_unit']=bcdiv( $operation->det->array[$i]['oc_amount'], $operation->det->array[$i]['qs_quantite'], 2); $result['operation'][$i]['vat']=$operation->det->array[$i]['oc_vat_amount']; } $result['operation'][$i]['vat_id']=$operation->det->array[$i]['qs_vat_code']; // // tva code for PEPPOL $x=$this->cn->get_row("select tva_peppol_code,tva_rate from tva_rate where tva_id=$1" ,[ $result['operation'][$i]['vat_id']]); $result['operation'][$i]['vat_code']=($x['tva_peppol_code']=="")?"S":$x['tva_peppol_code']; $result['operation'][$i]['vat_rate']=$x['tva_rate']; $result['operation'][$i]['vat_reversed']=$operation->det->array[$i]['qs_vat_sided']; } //------------------------------------------------ // retrieve order and comment //------------------------------------------------ $a_row=$this->cn->get_array("select id_type,ji_value from jrn_info where jr_id=$1" ,[$jr_id]); $nb_row = count($a_row); $result['info']=[]; $result['info']['order']='NA'; $result['info']['communication']=''; for($i=0;$i<$nb_row;$i++) { switch ($a_row[$i]['id_type']) { case 'BON_COMMANDE': $result['info']['order']=$a_row[$i]['ji_value']; break; case 'OTHER': $result['info']['communication']=$a_row[$i]['ji_value']; break; } } $result['info']['communication']=($result['info']['communication']=="")?$result['id']:$result['info']['communication']; $result['document']=$this->fill_document($jr_id); /** * Compute totals VAT and AMOUNT */ $nb_operation = count($result['operation']); /// block cac:LegalMonetaryTotal $result['LineExtensionAmount']=0; $result['TaxExclusiveAmount']=0; $result['TaxInclusiveAmount']=0; $result['PayableAmount']=0; // block cac:TaxTotal $result['TaxableAmount']=0; $result['TaxAmount']=0; // array for TaxSubtotal $VAT_SubTotal=array(); $idx_subtotal=0; bcscale(2); // for each operation $VAT_SubTotal=array(); for ($i=0;$i < $nb_operation;$i++) { $acc_tva=\Acc_TVA::build($this->cn,$result['operation'][$i]['vat_id'] ); $percent = bcmul($acc_tva->tva_rate,100,2); $idx=sprintf("%s - %s",$percent,$result['operation'][$i]['vat_code'] ); // subtotal for VAT $n = find_idx($VAT_SubTotal,'idx',$idx); if ($n == -1 ) { $n=$idx_subtotal; $VAT_SubTotal[$idx_subtotal]=array(); $VAT_SubTotal[$idx_subtotal]['idx']=$idx; $VAT_SubTotal[$idx_subtotal]['vat_code']=$result['operation'][$i]['vat_code'] ; if ( $acc_tva->tva_both_side == 1 ) $VAT_SubTotal[$idx_subtotal]['percent']=0; else $VAT_SubTotal[$idx_subtotal]['percent']=$percent; $VAT_SubTotal[$idx_subtotal]['vatex']=$acc_tva->vx_code; $VAT_SubTotal[$idx_subtotal]['amount']=$VAT_SubTotal[$idx_subtotal]['vat']=0; $idx_subtotal++; } /** * @todo Pour les intracomm , quel taux utilisé ? 0 ou 21% */ $VAT_SubTotal[$n]['amount']=bcadd($VAT_SubTotal[$n]['amount'],$result['operation'][$i]['price']); if ( $acc_tva->tva_both_side == 0 ) $VAT_SubTotal[$n]['vat']=bcadd($VAT_SubTotal[$n]['vat'],$result['operation'][$i]['vat']); $result['TaxableAmount']=bcadd( $result['TaxableAmount'],$result['operation'][$i]['price']); // $result['TaxAmount']=bcsub( $result['TaxAmount'],$result['operation'][$i]['vat_reversed']); $result['operation'][$i]['vat_percent']=$percent; if ( $acc_tva->tva_both_side == 1 ) { $result['operation'][$i]['vat_percent']=0; }else { $result['TaxAmount']=bcadd( $result['TaxAmount'],$result['operation'][$i]['vat']); } } $result['subTotalVAT']=$VAT_SubTotal; $result['LineExtensionAmount']= $result['TaxableAmount']; $result['TaxExclusiveAmount']= $result['TaxableAmount']; $result['TaxInclusiveAmount']=bcadd( $result['TaxableAmount'],$result['TaxAmount']); $result['PayableAmount']=bcadd( $result['TaxableAmount'],$result['TaxAmount']); return $result; } /** * @brief make an array of parameter_extra where pe_code as key and pe_value * as value * @return array keys : pe_code,pe_value */ function load_noalyss_parameter() { $r=$this->cn->get_array(' select pe_code, pe_value from parameter_extra union all select pr_id,pr_value from parameter'); return array_column($r,"pe_value","pe_code"); } /** * @brief create an XML invoice based on JRN.JR_ID operation. * The PDF file could be added afterward. * @parameter $jr_id (int) operation JRN.JR_ID operation */ abstract function make_xml($jr_id); /** * @brief check that mandatory info are saved in the DB for company (seller) */ abstract function check_company_data() ; /** * @brief check that mandatory info are saved in the DB for customer */ abstract function check_customer_data() ; /** * @brief create the invoice in the right format, with PDF if any * @param $operation_id (int) JRN.JR_ID * @return string : XML or PDF format */ abstract function create_invoice($operation_id) ; /** * @brief display_error display a warning with all error */ public function display_error() { $a_error=$this->verify(); include NOALYSS_TEMPLATE."/xmlinvoice-display_error.php"; } /** * @brief check that the VAT is using a PEPPOL Code */ abstract function check_VAT(); /** * @brief thanks MY_INVOICE_FORMAT , create the corresponding object * - UBL21BEL => InvoiceUBL21 * - FacturX => FACTURXFR * @returns null MY_INVOICE_FORMAT is BASIC */ static function build_xmlinvoice(\Database $conx) { global $g_parameter; if ($g_parameter->MY_INVOICE_FORMAT == 'UBL21BEL') { return new \Noalyss\XMLDocument\InvoiceUBL21($conx); } if ($g_parameter->MY_INVOICE_FORMAT == 'FACTURXFR') { return new \Noalyss\XMLDocument\FacturX($conx); } return null; } /** * @brief check that all the data are correct * @returns empty arry : no errors, array with error code * @see get_message_error */ public function verify() { // verify all VAT ///@var $a_error : array of error_code see check_company_error $a_error = array(); $a_error['general'] = []; $a_error['operation']=[]; // verify that all needed data in PARAMETER are valid $a_error['company'] = $this->check_company_data(); $a_error['customer'] = $this->check_customer_data(); return $a_error; } /** * @brief retrieve data from customer and return it into an array * @param $card_id (int) FICHE.F_ID * @return array keys : * - name * - ,street * - ,postalzone * - ,city * - ,country * - ,customer_vat_id => VAT Number * - , registration_name, * - card_id * - endpoint_id * @note : * endpoint_id: normally it this the VAT number (BE included) and scheme 9925 or the scheme 00208 VAT number without BE (enterprise number) */ function fill_customer($card_id):array { $customer =new \Fiche($this->cn,$card_id); $result=array(); $result['card_id']=$card_id; $result['name']=$customer->get_attribute(ATTR_DEF_NAME,0); $result['street']=$customer->get_attribute(ATTR_DEF_ADRESS,0); $result['postalzone']=$customer->get_attribute(ATTR_DEF_POSTCODE,0); $result['city']=$customer->get_attribute(ATTR_DEF_CITY,0); // official ID , like VAT $result['customer_vat_id']=str_replace([" ",".","-","/"],"" ,$customer->get_attribute(ATTR_DEF_NUMTVA,0)); // official name of the company $result['registration_name']=$customer->get_attribute(ATTR_DEF_NAME,0); // find country_code of this card $result['country']=$customer->get_attribute(ATTR_DEF_COUNTRY_CODE,0); if ( $result['country'] == "") { $result['country']=substr($result['customer_vat_id'],0,2); } $result['endpoint_id']=$customer->get_attribute(ATTR_DEF_PEPPOLID,0); return $result; } /** * @brief complete $this->data from $g_parameter (global variable) for * Noalyss_Folder_Parameter * @return array keys : * - name * - ,street * - ,postalzone * - ,city * - ,country * - supplier_vat_id => VAT Number avec BE ! * - registration_name, * */ function fill_supplier():array { $a_parameter=$this->load_noalyss_parameter(); $result=array(); $result['name']=$a_parameter['MY_NAME']; $result['street']=$a_parameter['MY_STREET']; $result['postalzone']=$a_parameter['MY_POSTCODE']; $result['city']=$a_parameter['MY_CITY']; $result['country']=$a_parameter['MY_COUNTRY']; // official name of the company $result['registration_name']=$a_parameter['MY_NAME']; // official ID , like VAT $result['supplier_vat_id']=str_replace([" ",".","-","/"],"" ,$a_parameter['MY_TVA']); /** * @TODO vérifier qu'il contient bien BE */ $result['COUNTRY_CODE']=$a_parameter['MY_COUNTRY_CODE']?? substr($result['supplier_vat_id'], 0, 2); $result['COMPANY_LEGAL_REGISTRATION']=$a_parameter['COMPANY_LEGAL_REGISTRATION']??""; $result['COMPANY_LEGAL_ENTITY']=$a_parameter['COMPANY_LEGAL_ENTITY']??""; $result['INVOICE_CONTACT_NAME']=$a_parameter['INVOICE_CONTACT_NAME']??""; $result['INVOICE_EMAIL_COMPANY']=$a_parameter['INVOICE_EMAIL_COMPANY']??""; $result['COMPANY_PEPPOL_ID']=$a_parameter['COMPANY_PEPPOL_ID']??""; return $result; } /** * @brief build operation from array * key : * - [e_march0] => Quick code of the item - [e_march0_label] => Label of item - [e_march0_price] => Unit Price - [e_quant0] => Quantity - [htva_march0] => Price w/0 VAT - [e_march0_tva_id] => Code VAT - [e_march0_tva_amount] => Amount VAT - [tva_march0] => Amount VAT (duplicate -> to remove) - [tvac_march0] => Total Amount Tax included * @param type $a_array * @return type */ function fill_operation_from_array($a_array) { $result=array(); $http=new \HttpInput(); $http->set_array($a_array); $nb_item=$http->get_value("nb_item"); for ($i=0;$i<$nb_item;$i++) { if ( $http->get_value("e_march{$i}_tva_id") == "") { continue; } $operation=array(); $card=\Fiche::from_qcode($this->cn,trim($http->get_value("e_march{$i}"))); $operation['card_id']=$card->id; $operation['quantity']=$http->get_value("e_quant{$i}"); $operation['price']=$http->get_value("e_march{$i}_price"); $operation['vat']=$http->get_value("tvac_march{$i}"); $tva= \Acc_Tva::build($this->cn, $http->get_value("e_march{$i}_tva_id")); $operation['vat_id']=$tva->tva_id; $operation['vat_reversed']=($tva->tva_both_side==1)?$operation['vat']:0; $operation['vat_code']=$tva->tva_peppol_code; $operation['code_quantity']=$card->get_attribute(ATTR_DEF_QUANTITY_TYPE,0); $operation['code_quantity']=($operation['code_quantity']=="")?"EA":$operation['code_quantity']; $result[$i]=$operation; } return $result; } /** * @brief set the PDF * @param $pdf_filename (string) full path to the PDF * @return $this * @throws \Exception if the filename doesn't exist */ public function set_pdf_filename($pdf_filename) { if ( !file_exists($pdf_filename)) { throw new \Exception("AD65 $pdf_filename doesn't not exist"); } $this->pdf_filename = $pdf_filename; return $this; } /** * @brief retrieve additionnal documents but only PDF , not other files * @param $jr_id (int) JRN.JR_DEF_ID * @return array : empty or keys= (filename,description, log id) from table from JRN_SUP_DOCUMENT */ public function fill_document($jr_id) { $result=[]; $a_document=$this->cn->get_array("select js_id,js_filename,js_description,js_lob from jrn_sup_document where js_mimetype='application/pdf' AND jr_id=$1", [$jr_id]); if ($a_document == null) return array(); foreach ($a_document as $document) { $result[]=array("filename"=>$document['js_filename'] ,"description"=>$document['js_description'] ,'oid'=>$document['js_lob'] ,'id'=>$document['js_id'] ); } return $result; } }