noalyss Version-10
NOALYSS : serveur de comptabilité et ERP (2002)
Loading...
Searching...
No Matches
xmlinvoice.class.php
Go to the documentation of this file.
1<?php
2namespace Noalyss\XMLDocument;
3
4//use Noalyss\Utility;
5/*
6 * This file is part of NOALYSS.
7 *
8 * NOALYSS is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * NOALYSS is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with NOALYSS; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21*/
22// Copyright Author Dany De Bontridder danydb@aevalys.eu 22/10/23
23
24/**
25 * @file
26 * @brief mother for e-invoice : ubl2.1 , Factur-X
27 */
28/**
29 * @class XMLInvoice
30 * @brief Mother class for e-invoice
31 * - $data is
32 * * @code
33(
34 [id] => 25.822
35 [issue_date] => 2025-06-10
36 [due_date] =>
37 [supplier] => Array
38 (
39 [name] => My company sprl
40 [street] => Allée des Zoulons
41 [postalzone] => 1080
42 [city] => Molenbeek Saint Jean
43 [country] => BE
44 [supplier_vat_id] => BE012345678
45 [registration_name] => My Company sprl
46 )
47
48 [customer] => Array
49 (
50 [name] => This asbl
51 [street] =>
52 [postalzone] =>
53 [city] =>
54 [country] =>
55 [customer_vat_id] => numéro TVA
56 [registration_name] => This asbl
57 )
58
59 [currency] => 0
60 [info] => Array
61 [order] = order reference
62 [communication] = communication added to the invoice
63 [operation] => Array
64 (
65 [0] => Array
66 (
67 [card_id] => 568
68 [quantity] => 1.0000
69 [price] => 10.0000
70 [vat] => 2.1000
71 [vat_id] => 1
72 [vat_reversed] => 0.0000
73 [code_quantity]=> EA
74 [vat_code]=> Code VAT for PEPPOL (S,K,...)
75 )
76
77 [1] => Array
78 (
79 [card_id] => 164
80 [quantity] => 5.0000
81 [price] => 83.4500
82 [vat] => 17.5200
83 [vat_id] => 1
84 [vat_reversed] => 0.0000
85 [code_quantity]=> EA
86 [vat_code]=> Code VAT for PEPPOL (S,K,...)
87 )
88
89 [2] => Array
90 (
91 [card_id] => 483
92 [quantity] => 1.0000
93 [price] => 72.0000
94 [vat] => 15.1200
95 [vat_id] => 5
96 [vat_reversed] => 15.1200
97 [code_quantity]=> EA
98 [vat_code]=> Code VAT for PEPPOL (S,K,...)
99 )
100
101 )
102
103 *
104 *
105)
106
107
108 * @endcode
109 *
110 */
111abstract class XMLInvoice extends \DOMDocument
112{
113 protected $cn; //!< Database conx , current folder
114 protected $data; //! $data (Array) data retrieve from DB
115 protected $jr_id; //! $jr_id (int) is JRN.JR_ID
116 function __construct(\Database $conx)
117 {
118 parent::__construct("1.0", "UTF-8");
119 $this->cn=$conx;
120 $this->data=[];
121 }
122 /**
123 * @brief return data
124 * @return array
125 */
126 public function get_data() {
127 return $this->data;
128 }
129 /**
130 * @brief returns data
131 * @param $data (array)
132 * @return XMLInvoice
133 */
134 public function set_data($data): XMLInvoice {
135 $this->data = $data;
136 return $this;
137 }
138
139 /**
140 * @brief get Database Connexion
141 * @param $cn (\Database)
142 * @return XMLInvoice
143 */
144
145 public function get_db_conx():\Database {
146 return $this->cn;
147 }
148 /**
149 * @brief set Database Connexion
150 * @param $cn (\Database)
151 * @return XMLInvoice
152 */
153 public function set_db_conx(\Database $cn): XMLInvoice {
154 $this->cn = $cn;
155 return $this;
156 }
157
158 /**
159 * @brief transform an operation ($jr_id) into an array, which contains
160 * needed information for making an e-invoice
161 * @param $jr_id (int) operation JRN.JR_ID
162 * @return array with all info7
163
164 *
165 */
166 function build_data($jr_id):array
167 {
168 global $g_parameter;
169 $this->jr_id=$jr_id;
170 $operation = new \Acc_Sold($this->cn,$jr_id);
171
172 $operation->get();
173 $result=array();
174 $result["id"]= $operation->det->jr_pj_number;
175 $result["issue_date"]=$operation->det->jr_date;
176 $result["due_date"]=($operation->det->jr_ech=="")?$operation->det->jr_date:$operation->det->jr_ech;
177 // supplier
178 $result['supplier']=$this->fill_supplier();
179
180
181 //customer
182 $result['customer']=$this->fill_customer($operation->det->array[0]['qs_client']);
183
184 // currency
185 $result['currency']=$this->cn->get_value("select cr_code_iso from currency where id=$1"
186 ,array($operation->det->currency_id));
187
188 // document description
189 $result['description']=$operation->det->jr_comment;
190
191 // note if any
192
193 $result['note']= $result['description'];
194 $a_note = $this->cn->get_array("select n_id,n_text from jrn_note where jr_id=$1",[$operation->det->jr_id]);
195 $nb_note=count($a_note);
196 for ($e=0;$e<$nb_note;$e++)
197 {
198
199 $result['note'].= html_entity_decode($a_note[$e]['n_text']??"");
200 }
201 // goods and services
202 $result['operation']=array();
203
204 $nb_operation= count($operation->det->array);
205 for ($i=0;$i < $nb_operation;$i++) {
206 $result['operation'][$i]['card_id']=$operation->det->array[$i]['qs_fiche'];
207 $result['operation'][$i]['quantity']=$operation->det->array[$i]['qs_quantite'];
208 $card=new \Fiche($this->cn,$operation->det->array[$i]['qs_fiche']);
209 $result['operation'][$i]['qcode']=$card->get_attribute(ATTR_DEF_QUICKCODE);
210 $result['operation'][$i]['name']=$card->get_attribute(ATTR_DEF_NAME);
211 $result['operation'][$i]['description']=($operation->det->array[$i]['j_text']=="")?$card->get_attribute(9):$operation->det->array[$i]['j_text'];
212
213 // get the type of unity, if not found then it will be EA
214 $x= $card->get_attribute(ATTR_DEF_QUANTITY_TYPE,0);
215 $result['operation'][$i]['code_quantity']=($x===false||$x=="")?"EA":$x;
216
217 // $operation->det->currency_id == 0 default currency of the folder
218 if ($operation->det->currency_id == 0 ) {
219 $result['operation'][$i]['price']=$operation->det->array[$i]['qs_price'];
220 $result['operation'][$i]['price_unit']=$operation->det->array[$i]['qs_unit'];
221 $result['operation'][$i]['vat']=$operation->det->array[$i]['qs_vat'];
222 } else {
223 $result['operation'][$i]['price']=$operation->det->array[$i]['oc_amount'];
224 $result['operation'][$i]['price_unit']=bcdiv(
225 $operation->det->array[$i]['oc_amount'],
226 $operation->det->array[$i]['qs_quantite'],
227 2);
228 $result['operation'][$i]['vat']=$operation->det->array[$i]['oc_vat_amount'];
229
230 }
231 $result['operation'][$i]['vat_id']=$operation->det->array[$i]['qs_vat_code'];
232// // tva code for PEPPOL
233 $x=$this->cn->get_row("select tva_peppol_code,tva_rate from tva_rate where tva_id=$1"
234 ,[ $result['operation'][$i]['vat_id']]);
235 $result['operation'][$i]['vat_code']=($x['tva_peppol_code']=="")?"S":$x['tva_peppol_code'];
236 $result['operation'][$i]['vat_rate']=$x['tva_rate'];
237
238 $result['operation'][$i]['vat_reversed']=$operation->det->array[$i]['qs_vat_sided'];
239 }
240 //------------------------------------------------
241 // retrieve order and comment
242 //------------------------------------------------
243 $a_row=$this->cn->get_array("select id_type,ji_value from jrn_info where jr_id=$1"
244 ,[$jr_id]);
245 $nb_row = count($a_row);
246 $result['info']=[];
247 $result['info']['order']='NA';
248 $result['info']['communication']='';
249 for($i=0;$i<$nb_row;$i++) {
250 switch ($a_row[$i]['id_type']) {
251 case 'BON_COMMANDE':
252 $result['info']['order']=$a_row[$i]['ji_value'];
253 break;
254 case 'OTHER':
255 $result['info']['communication']=$a_row[$i]['ji_value'];
256 break;
257
258 }
259 }
260 $result['info']['communication']=($result['info']['communication']=="")?$result['id']:"";
261 $result['document']=$this->fill_document($jr_id);
262
263 /**
264 * Compute totals VAT and AMOUNT
265 */
266 $nb_operation = count($result['operation']);
267
268 /// block cac:LegalMonetaryTotal
269 $result['LineExtensionAmount']=0;
270 $result['TaxExclusiveAmount']=0;
271 $result['TaxInclusiveAmount']=0;
272 $result['PayableAmount']=0;
273
274 // block cac:TaxTotal
275 $result['TaxableAmount']=0;
276 $result['TaxAmount']=0;
277
278 // array for TaxSubtotal
279 $VAT_SubTotal=array();
280 $idx_subtotal=0;
281 bcscale(2);
282 // for each operation
283 $VAT_SubTotal=array();
284 for ($i=0;$i < $nb_operation;$i++) {
285 $acc_tva=\Acc_TVA::build($this->cn,$result['operation'][$i]['vat_id'] );
286 $percent = bcmul($acc_tva->tva_rate,100,2);
287 $idx=sprintf("%s - %s",$percent,$result['operation'][$i]['vat_code'] );
288 // subtotal for VAT
289 $n = find_idx($VAT_SubTotal,'idx',$idx);
290 if ($n == -1 ) {
291 $n=$idx_subtotal;
292 $VAT_SubTotal[$idx_subtotal]=array();
293 $VAT_SubTotal[$idx_subtotal]['idx']=$idx;
294 $VAT_SubTotal[$idx_subtotal]['vat_code']=$result['operation'][$i]['vat_code'] ;
295 $VAT_SubTotal[$idx_subtotal]['percent']=$percent;
296 $VAT_SubTotal[$idx_subtotal]['vatex']=$acc_tva->vx_code;
297 $VAT_SubTotal[$idx_subtotal]['amount']=$VAT_SubTotal[$idx_subtotal]['vat']=0;
298
299 $idx_subtotal++;
300 }
301 /**
302 * @todo Pour les intracomm , quel taux utilisé ? 0 ou 21%
303 */
304 $VAT_SubTotal[$n]['amount']=bcadd($VAT_SubTotal[$n]['amount'],$result['operation'][$i]['price']);
305 $VAT_SubTotal[$n]['vat']=bcadd($VAT_SubTotal[$n]['vat'],$result['operation'][$i]['vat']);
306 $VAT_SubTotal[$n]['vat']=bcsub($VAT_SubTotal[$n]['vat'],$result['operation'][$i]['vat_reversed']);
307 $result['TaxableAmount']=bcadd( $result['TaxableAmount'],$result['operation'][$i]['price']);
308 $result['TaxAmount']=bcadd( $result['TaxAmount'],$result['operation'][$i]['vat']);
309 $result['TaxAmount']=bcsub( $result['TaxAmount'],$result['operation'][$i]['vat_reversed']);
310 $result['operation'][$i]['vat_percent']=$percent;
311 }
312 $result['subTotalVAT']=$VAT_SubTotal;
313 $result['LineExtensionAmount']= $result['TaxableAmount'];
314 $result['TaxExclusiveAmount']= $result['TaxableAmount'];
315 $result['TaxInclusiveAmount']=bcadd( $result['TaxableAmount'],$result['TaxAmount']);
316 $result['PayableAmount']=bcadd( $result['TaxableAmount'],$result['TaxAmount']);
317
318 return $result;
319
320 }
321
322
323 /**
324 * @brief make an array of parameter_extra where pe_code as key and pe_value
325 * as value
326 * @return array keys : pe_code,pe_value
327 */
329 {
330 $r=$this->cn->get_array('
331 select pe_code, pe_value from parameter_extra
332 union all
333 select pr_id,pr_value
334 from parameter');
335
336 return array_column($r,"pe_value","pe_code");
337 }
338 /**
339 * @brief create an XML invoice based on JRN.JR_ID operation.
340 * The PDF file could be added afterward.
341 * @parameter $jr_id (int) operation JRN.JR_ID operation
342 */
343 abstract function make_xml($jr_id);
344 /**
345 * @brief check that mandatory info are saved in the DB for company (seller)
346 */
347 abstract function check_company_data() ;
348 /**
349 * @brief check that mandatory info are saved in the DB for customer
350 */
351 abstract function check_customer_data() ;
352 /**
353 * @brief create the invoice in the right format, with PDF if any
354 * @param $operation_id (int) JRN.JR_ID
355 * @return string : XML or PDF format
356 */
357 abstract function create_invoice($operation_id) ;
358
359 /**
360 * @brief display_error display a warning with all error
361 */
362 public function display_error()
363 {
364 $a_error=$this->verify();
365 include NOALYSS_TEMPLATE."/xmlinvoice-display_error.php";
366
367 }
368 /**
369 * @brief check that the VAT is using a PEPPOL Code
370 */
371 abstract function check_VAT();
372
373 /**
374 * @brief thanks MY_INVOICE_FORMAT , create the corresponding object
375 * - UBL21BEL => InvoiceUBL21
376 * - FacturX => FACTURXFR
377 * @returns null MY_INVOICE_FORMAT is BASIC
378 */
379 static function build_xmlinvoice(\Database $conx) {
380 global $g_parameter;
381 if ($g_parameter->MY_INVOICE_FORMAT == 'UBL21BEL') {
382 return new \Noalyss\XMLDocument\InvoiceUBL21($conx);
383 }
384 if ($g_parameter->MY_INVOICE_FORMAT == 'FACTURXFR') {
385 return new \Noalyss\XMLDocument\FacturX($conx);
386 }
387 return null;
388 }
389
390 /**
391 * @brief check that all the data are correct
392 * @returns empty arry : no errors, array with error code
393 * @see get_message_error
394 */
395 public function verify()
396 {
397 // verify all VAT
398 ///@var $a_error : array of error_code see check_company_error
399 $a_error = array();
400 $a_error['general'] = [];
401 $a_error['operation']=[];
402
403 // verify that all needed data in PARAMETER are valid
404 $a_error['company'] = $this->check_company_data();
405 $a_error['customer'] = $this->check_customer_data();
406
407 return $a_error;
408 }
409
410 /**
411 * @brief retrieve data from customer and return it into an array
412 * @param $card_id (int) FICHE.F_ID
413 * @return array keys :
414 * - name
415 * - ,street
416 * - ,postalzone
417 * - ,city
418 * - ,country
419 * - ,customer_vat_id => VAT Number
420 * - , registration_name,
421 * - card_id
422 * - endpoint_id
423 * @note :
424 * endpoint_id:
425 normally it this the VAT number (BE included) and scheme 9925
426 or the scheme 00208 VAT number without BE (enterprise number)
427
428 */
429 function fill_customer($card_id):array
430 {
431 $customer =new \Fiche($this->cn,$card_id);
432 $result=array();
433 $result['card_id']=$card_id;
434 $result['name']=$customer->get_attribute(ATTR_DEF_NAME,0);
435 $result['street']=$customer->get_attribute(ATTR_DEF_ADRESS,0);
436 $result['postalzone']=$customer->get_attribute(ATTR_DEF_POSTCODE,0);
437 $result['city']=$customer->get_attribute(ATTR_DEF_CITY,0);
438
439
440 // official ID , like VAT
441 $result['customer_vat_id']=str_replace([" ",".","-","/"],"" ,$customer->get_attribute(ATTR_DEF_NUMTVA,0));
442 // official name of the company
443 $result['registration_name']=$customer->get_attribute(ATTR_DEF_NAME,0);
444 // find country_code of this card
445 $result['country']=$customer->get_attribute(ATTR_DEF_COUNTRY_CODE,0);
446 if ( $result['country'] == "")
447 {
448 $result['country']=substr($result['customer_vat_id'],0,2);
449 }
450 $result['endpoint_id']=$customer->get_attribute(ATTR_DEF_PEPPOLID,0);
451
452
453 return $result;
454 }
455 /**
456 * @brief complete $this->data from $g_parameter (global variable) for
457 * Noalyss_Folder_Parameter
458 * @return array keys :
459 * - name
460 * - ,street
461 * - ,postalzone
462 * - ,city
463 * - ,country
464 * - supplier_vat_id => VAT Number avec BE !
465 * - registration_name,
466 *
467 */
468 function fill_supplier():array
469 {
470 $a_parameter=$this->load_noalyss_parameter();
471 $result=array();
472 $result['name']=$a_parameter['MY_NAME'];
473 $result['street']=$a_parameter['MY_STREET'];
474 $result['postalzone']=$a_parameter['MY_POSTCODE'];
475 $result['city']=$a_parameter['MY_CITY'];
476 $result['country']=$a_parameter['MY_COUNTRY'];
477 // official name of the company
478 $result['registration_name']=$a_parameter['MY_NAME'];
479 // official ID , like VAT
480 $result['supplier_vat_id']=str_replace([" ",".","-","/"],"" ,$a_parameter['MY_TVA']);
481 /**
482 * @TODO vérifier qu'il contient bien BE
483 */
484
485 $result['COUNTRY_CODE']=$a_parameter['MY_COUNTRY_CODE']?? substr($result['supplier_vat_id'], 0, 2);
486 $result['COMPANY_LEGAL_REGISTRATION']=$a_parameter['COMPANY_LEGAL_REGISTRATION']??"";
487 $result['COMPANY_LEGAL_ENTITY']=$a_parameter['COMPANY_LEGAL_ENTITY']??"";
488 $result['INVOICE_CONTACT_NAME']=$a_parameter['INVOICE_CONTACT_NAME']??"";
489 $result['INVOICE_EMAIL_COMPANY']=$a_parameter['INVOICE_EMAIL_COMPANY']??"";
490 $result['COMPANY_PEPPOL_ID']=$a_parameter['COMPANY_PEPPOL_ID']??"";
491 return $result;
492 }
493 /**
494 * @brief build operation from array
495 * key :
496 * - [e_march0] => Quick code of the item
497 - [e_march0_label] => Label of item
498 - [e_march0_price] => Unit Price
499 - [e_quant0] => Quantity
500 - [htva_march0] => Price w/0 VAT
501 - [e_march0_tva_id] => Code VAT
502 - [e_march0_tva_amount] => Amount VAT
503 - [tva_march0] => Amount VAT (duplicate -> to remove)
504 - [tvac_march0] => Total Amount Tax included
505 * @param type $a_array
506 * @return type
507 */
508 function fill_operation_from_array($a_array)
509 {
510 $result=array();
511 $http=new \HttpInput();
512 $http->set_array($a_array);
513
514 $nb_item=$http->get_value("nb_item");
515 for ($i=0;$i<$nb_item;$i++)
516 {
517 if ( $http->get_value("e_march{$i}_tva_id") == "")
518 {
519 continue;
520 }
521 $operation=array();
522 $card=\Fiche::from_qcode($this->cn,trim($http->get_value("e_march{$i}")));
523 $operation['card_id']=$card->id;
524 $operation['quantity']=$http->get_value("e_quant{$i}");
525 $operation['price']=$http->get_value("e_march{$i}_price");
526 $operation['vat']=$http->get_value("tvac_march{$i}");
527 $tva= \Acc_Tva::build($this->cn, $http->get_value("e_march{$i}_tva_id"));
528 $operation['vat_id']=$tva->tva_id;
529 $operation['vat_reversed']=($tva->tva_both_side==1)?$operation['vat']:0;
530 $operation['vat_code']=$tva->tva_peppol_code;
531
532 $operation['code_quantity']=$card->get_attribute(ATTR_DEF_QUANTITY_TYPE,0);
533 $operation['code_quantity']=($operation['code_quantity']=="")?"EA":$operation['code_quantity'];
535 }
536 return $result;
537 }
538
539 /**
540 * @brief set the PDF
541 * @param $pdf_filename (string) full path to the PDF
542 * @return $this
543 * @throws \Exception if the filename doesn't exist
544 */
545 public function set_pdf_filename($pdf_filename) {
546 if ( !file_exists($pdf_filename)) {
547 throw new \Exception("AD65 $pdf_filename doesn't not exist");
548 }
549 $this->pdf_filename = $pdf_filename;
550 return $this;
551 }
552 /**
553 * @brief retrieve additionnal documents but only PDF , not other files
554 * @param $jr_id (int) JRN.JR_DEF_ID
555 * @return array : empty or keys= (filename,description, log id) from table from JRN_SUP_DOCUMENT
556 */
557 public function fill_document($jr_id)
558 {
559 $result=[];
560 $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",
561 [$jr_id]);
562 if ($a_document == null) return array();
563
564 foreach ($a_document as $document)
565 {
566 $result[]=array("filename"=>$document['js_filename']
567 ,"description"=>$document['js_description']
568 ,'oid'=>$document['js_lob']
569 ,'id'=>$document['js_id']
570 );
571 }
572 return $result;
573
574 }
575
576}
find_idx($array, $key, $value)
retrieve the index for the key percent, returns -1 if nothing found
global $g_parameter
catch(Exception $exc) if(! $g_user->can_write_action($ag_id)) $r
$op jr_id
$percent
$input_from cn
static build($db, $p_code)
retrieve TVA rate thanks the code that could be the tva_id or tva_code.
contains the class for connecting to Noalyss
static from_qcode(Database $cn, string $p_qcode)
create a card from a qcode and returns a card
Mother class for e-invoice.
$cn
Database conx , current folder.
$jr_id
$data (Array) data retrieve from DB
load_noalyss_parameter()
make an array of parameter_extra where pe_code as key and pe_value as value
display_error()
display_error display a warning with all error
check_VAT()
check that the VAT is using a PEPPOL Code
fill_document($jr_id)
retrieve additionnal documents but only PDF , not other files
get_db_conx()
get Database Connexion
check_company_data()
check that mandatory info are saved in the DB for company (seller)
set_db_conx(\Database $cn)
set Database Connexion
fill_operation_from_array($a_array)
build operation from array key :
static build_xmlinvoice(\Database $conx)
thanks MY_INVOICE_FORMAT , create the corresponding object
__construct(\Database $conx)
$jr_id (int) is JRN.JR_ID
check_customer_data()
check that mandatory info are saved in the DB for customer
fill_customer($card_id)
retrieve data from customer and return it into an array
build_data($jr_id)
transform an operation ($jr_id) into an array, which contains needed information for making an e-invo...
set_pdf_filename($pdf_filename)
set the PDF
create_invoice($operation_id)
create the invoice in the right format, with PDF if any
make_xml($jr_id)
create an XML invoice based on JRN.JR_ID operation.
fill_supplier()
complete $this->data from $g_parameter (global variable) for Noalyss_Folder_Parameter
$n
Definition compute.php:54
const ATTR_DEF_ADRESS
Definition constant.php:230
const ATTR_DEF_NUMTVA
Definition constant.php:229
const ATTR_DEF_NAME
Definition constant.php:223
const ATTR_DEF_COUNTRY_CODE
Definition constant.php:255
const ATTR_DEF_PEPPOLID
Definition constant.php:256
const ATTR_DEF_POSTCODE
Definition constant.php:231
const ATTR_DEF_QUANTITY_TYPE
Definition constant.php:257
const ATTR_DEF_CITY
Definition constant.php:236
const ATTR_DEF_QUICKCODE
Definition constant.php:244
$nb_operation