Merge branch '251029-einvoice' into unstable

* 251029-einvoice:
  QuickCode : default = base for accounting + card name
  Cosmetic : set layer correctly
  ICARD: improve, action in javascript after cleaning the input of the qcode
  VAT : fix bug when VAT not usable for ledger or purchase
  cosmetic : padding inner_box
  COSMETIC: z-index computed properly for managetable and search_account
  FIX : problem with VAT SELECT: cannot update the right DOMID
  E-INVOICE : VATEX filter on the country
  E-INVOICE : add VATEX CODE in C0VAT E-INVOICE vatex code check when computing an invoice
  Improve SELECT_BOX
  PHP8.2 deprecated: dynamic properties + cosmetic menu
  Code improvement and ergonomy for menu display
  e-invoice : import creditNote and create a PDF
  use protected instead of private, remove debug info
  e-invoice : make a PDF with credit note
  Supplementary documents for an operation, an operation can have several attached documents
  Improve: message for uploading , compute ini value and color red for the trash can
  E-INVOICE extract all documents from XML and store in DB
  E-INVOICE: Peppol id is used to compose the UBL21, improve check and use
This commit is contained in:
sparkyx 2025-11-22 14:54:50 +01:00
commit 64c42b1c20
67 changed files with 2647 additions and 707 deletions

View file

@ -314,76 +314,61 @@ class Acc_Document extends Document {
jr_id=$2",
[$oid,$this->d_id]);
$xmlreader= XMLInvoice_Reader::build_from_file($a_file['filename']);
$xmlreader= \Noalyss\XMLDocument\XML_Reader::build_from_file($a_file['filename']);
//@var $embedded_file (array) keys = filecontent: binary data
//,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;
@ -410,5 +395,42 @@ class Acc_Document extends Document {
.'</a>';
return $r;
}
static function display_supplementary_doc($cn,$div,$jr_id)
{
$gDossier=\Dossier::id();
$q=new Jrn_Sup_Document_SQL($cn);
$a_row=$q->collect_objects(" where jr_id=$1 order by js_cbc_id", [$jr_id]);
if ( count($a_row) > 0)
{
foreach ($a_row as $item) {
$export="export.php?";
$script="Supplement_Document.delete_document('$gDossier','$div','{$item->js_id}','$jr_id')";
$rowid=sprintf("row_js_%s_%s",$div,$item->js_id);
// @var $download (url) to send file
$download="export.php?". http_build_query(
[
"act"=>"RAW:suppl-document"
,"js_id"=>$item->js_id
,"gDossier"=>$gDossier
]);
?>
<div class="row" id="<?=$rowid?>">
<div class="col">
<a href="<?=$download?>" download> <?=$item->js_filename?></a>
</div>O
<div class="col">
<?=$item->js_description?>
</div>
<div class="col">
<?=\Icon_Action::trash(uniqid("sdd"),$script)?>
</div>
</div>
<?php
}// end foreach $a_row
}// end if count
//download ALL files from this operation
}
}

View file

@ -1113,7 +1113,6 @@ class Acc_Ledger extends jrn_def_sql
$quick_code->value=(isset(${'qc_'.$i}))?${'qc_'.$i}:"";
$quick_code->readonly=$p_readonly;
$label='';
if ($quick_code->value!='')
{

View file

@ -1492,6 +1492,7 @@ class Acc_Ledger_Purchase extends Acc_Ledger
$W1->name="e_march".$i;
$W1->value=$march;
$W1->table=0;
$W1->setAfter_clean("compute_all_ledger()");
$W1->set_dblclick("fill_ipopcard(this);");
$W1->set_attribute('ipopup','ipopcard');

View file

@ -1623,6 +1623,7 @@ EOF;
$W1->name = "e_march" . $i;
$W1->value = $march;
$W1->table = 0;
$W1->setAfter_clean("compute_all_ledger()");
$W1->set_attribute('typecard', 'cred');
$W1->set_dblclick("fill_ipopcard(this);");
$W1->set_attribute('ipopup', 'ipopcard');

View file

@ -196,7 +196,7 @@ class Acc_Ledger_Search
$f_qcode->javascript=sprintf(' onchange="fill_data_onchange(%s);" ',
$f_qcode->name);
$f_qcode->value=$http->request($this->div.'qcode',"string","");
$f_qcode->setAfter_clean("");
/* $f_txt_qcode=new IText('qcode');
$f_txt_qcode->value=(isset($_REQUEST['qcode']))?$_REQUEST['qcode']:'';
*/

View file

@ -32,6 +32,7 @@
"comment"=>"tva_comment",
"account"=>"tva_poste"
tva_peppol_code
'vx_code'
* );
*/
@ -45,7 +46,9 @@ class Acc_Tva
"both_side"=>'tva_both_side',
'tva_reverse_account'=>'tva_reverse_account',
'tva_code'=>'tva_code',
"tva_peppol_code"=>"tva_peppol_code"
"tva_peppol_code"=>"tva_peppol_code",
'vx_code'=>'vx_code'
);
public $tva_id,
$tva_label,
@ -55,7 +58,8 @@ class Acc_Tva
$tva_both_side,
$tva_code,
$tva_reverse_account,
$tva_peppol_code
$tva_peppol_code,
$vx_code
;
private $cn; //!< Database connection
@ -75,6 +79,7 @@ class Acc_Tva
$this->tva_code=&$this->tva_rate_sql->tva_code;
$this->tva_reverse_account=&$this->tva_rate_sql->tva_reverse_account;
$this->tva_peppol_code=&$this->tva_rate_sql->tva_peppol_code;
$this->vx_code=&$this->tva_rate_sql->vx_code;
}
/**
@ -110,7 +115,7 @@ class Acc_Tva
{
$idx=self::$variable[$p_string];
$this->$idx=$p_value;
}
}
else
throw new Exception("ACC108"."Attribut inexistant $p_string",EXC_INVALID);

View file

@ -38,7 +38,9 @@ class Anc_Balance_Double extends Anc_Print
*
* \return string
*/
public $from_poste2;
public $to_poste2;
public $pa_id2;
function display_html ()
{
if ($this->pa_id == $this->pa_id2) throw new Exception(_("Pas de croisement avec un seul plan"),1000);

View file

@ -31,6 +31,10 @@
class Anc_Table extends Anc_Acc_Link
{
public $card_poste;
private $aheader;
private $sql;
private $arow;
function __construct($p_cn)
{
parent::__construct($p_cn);

View file

@ -224,12 +224,25 @@ class Card_Property
}
if ($this->ad_id == ATTR_DEF_TVA) {
$result['input'] = new ITva_Popup('popup_tva');
$result['input'] = new ITva_Popup( "av_text" . $this->ad_id);
$result['input']->table = 0;
$result['input']->value = $this->av_text;
$result['label']=$this->ad_text;
return $result;
}
// Warning length quickcode
if ($this->ad_id == ATTR_DEF_QUICKCODE) {
$result['input'] = new IText();
$result['input']->css_size = "100%";
$result['input']->label = $this->ad_text;
$result['input']->name = "av_text" . $this->ad_id;
$result['input']->value = $this->av_text;
$result['input']->placeholder= "999NOM";
$result['bulle'] = Icon_Action::warnbulle(76);
$result['label']=$this->ad_text;
return $result;
}
switch ($this->ad_type) {
case 'text':
$result['input'] = new IText();
@ -303,10 +316,7 @@ class Card_Property
$result['bulle'] = Icon_Action::infobulle(21);
}
// Warning length quickcode
if ($this->ad_id == ATTR_DEF_QUICKCODE) {
$result['bulle'] = Icon_Action::warnbulle(76);
}
return $result;
}

View file

@ -524,6 +524,16 @@ class Fiche
{
$p_array["av_text".ATTR_DEF_QUICKCODE]="";
}
// by default the quick_code is the base account of the class + first letters of the name
if ( $p_array["av_text".ATTR_DEF_QUICKCODE] =="")
{
$base_acc=$this->cn->get_value("select fd_class_base from fiche_def where fd_id = $1",
[$p_fiche_def]);
$p_array["av_text".ATTR_DEF_QUICKCODE]=sprintf("%s%s"
, substr($base_acc,0, 3)
, substr($p_array["av_text".ATTR_DEF_NAME], 0, 4)
);
}
$sql=sprintf("select insert_quick_code(%d,'%s')", $fiche_id,
sql_string($p_array['av_text'.ATTR_DEF_QUICKCODE]));
$this->cn->exec_sql($sql);
@ -553,9 +563,9 @@ class Fiche
}
catch (Exception $e)
{
record_log("FIC603".$e->getMessage()." ".$e->getTraceAsString());
record_log($e);
$this->cn->rollback();
throw ($e);
throw (new \Exception ("F561 ". __CLASS__.".".__FUNCTION__,561,$e));
return;
}
return;

View file

@ -584,7 +584,7 @@ EOF;
/**
* @brief Display each row for the global
* @param $obj Parm_Periode_SQL
* @param $p_nb not used so far
* @param $p_nb used to highlight odd rows
* @param $p_js javascript variable
*/
static function display_row_global(Parm_Periode_SQL $obj, $p_nb, $p_js)

View file

@ -56,7 +56,8 @@ class Tva_Rate_MTable extends Manage_Table_SQL
$this->set_col_label("tva_reverse_account", _('Poste comptable autoliquidation'));
$this->set_col_label("tva_sale", _("TVA Vente (C)"));
$this->set_col_label("tva_purchase", _("TVA Achat (D)"));
$this->set_col_label("tva_peppol_code", _("Code Facture électronique"));
$this->set_col_label("tva_peppol_code", _("Code Facture électronique UBL"));
$this->set_col_label("vx_code", _("Code Exemption TVA (vatex)"));
$this->set_property_visible('tva_reverse_account', false);
$this->set_property_visible('tva_peppol_code', false);
@ -93,7 +94,8 @@ class Tva_Rate_MTable extends Manage_Table_SQL
'tva_payment_purchase'=>_('TVA due ou récupérable quand l\'opération est payée ou exécutée'),
'tva_payment_sale'=>_('TVA due ou récupérable quand l\'opération est payée ou exécutée'),
'tva_reverse_account'=>_("Forcer ce poste comptable pour autoliquidation : par défault, le poste d'autoliquidation est calculé : soit celui qui est en contrepartie, soit le même (voir manuel)"),
'tva_peppol_code'=>_("Code TVA est utilisé pour les factures électroniques, plus d'information dans le manuel")
'tva_peppol_code'=>_("Code TVA est utilisé pour les factures électroniques, plus d'information dans le manuel"),
'vx_code'=>_("Code exemption pour facture PEPPOL (ubl)")
);
}
@ -200,7 +202,19 @@ class Tva_Rate_MTable extends Manage_Table_SQL
,'E'=>_('E Exempté de TVA')
));
echo $text->input();
}elseif ($key == "tva_id") {
} elseif ($key == 'vx_code')
{
$text=\HtmlInput::hidden("vx_code",$value);
echo $text;
if ( $value !="") {
$value= \Icon_Action::trash(uniqid(),"vat_code.select_value('xx')").$value;
}
echo span($value,'id="vx_value"');
$js=sprintf("vat_code.list_vatex()");
echo \Icon_Action::icon_magnifier(uniqid(),$js );
}
elseif ($key == "tva_id") {
$inum=new INum($key,$value);
echo $inum->input();
echo \HtmlInput::hidden("old_tva_id",$value);
@ -231,9 +245,21 @@ class Tva_Rate_MTable extends Manage_Table_SQL
HtmlInput::hidden($key, $value)
);
}
echo '<td class="text-muted">';
echo '<td >';
if (isset ($this->a_comment[$key])) {
print '<p class="text-muted">';
echo $this->a_comment[$key];
print '</p>';
}
if( $key == 'vx_code') {
echo '<span id="vx_code_description">';
$row=$this->table->cn->get_row("select vx_code,vx_code_name,vx_description,vx_remark from vatex_code where vx_code=$1",
[$value]);
if ( ! empty ($row)) {
echo $row['vx_description'].span($row['vx_remark'],' class="text-muted" ');
}
echo '</span>';
}
echo '</td>';
}
@ -252,6 +278,7 @@ class Tva_Rate_MTable extends Manage_Table_SQL
$http=new \HttpInput();
$this->table->tva_reverse_account=$http->request('tva_reverse_account');
$this->table->tva_peppol_code=$http->request('tva_peppol_code');
$this->table->vx_code=$http->request('vx_code');
}
/**
@ -290,6 +317,7 @@ class Tva_Rate_MTable extends Manage_Table_SQL
$tva_rate->setp("tva_both_side", $this->table->tva_both_side);
$tva_rate->setp("tva_reverse_account", $this->table->tva_reverse_account);
$tva_rate->setp("tva_peppol_code", $this->table->tva_peppol_code);
$tva_rate->setp("vx_code", $this->table->vx_code);
// TVA accounting must be joined and separated with a comma
$tva_purchase=(trim($this->table->tva_purchase)=="")?"#":$this->table->tva_purchase;
@ -422,6 +450,23 @@ class Tva_Rate_MTable extends Manage_Table_SQL
if ( trim($this->table->tva_label??"")=="") {
$this->set_error("tva_label", _('Le label ne peut être vide'));
}
// if vatex is set then code invoice must be different from S and Z
if ( trim($this->table->vx_code??"") != "" && in_array($this->table->tva_peppol_code,['S','Z'] ))
{
$this->set_error("vx_code",_("Le code d'exemption TVA ne peut être utilisé avec ce code Facture électronique UBL "));
}
// if vatex is set then code invoice must be different from S and Z
if ( trim($this->table->vx_code??"") != "" && $this->table->tva_peppol_code=="")
{
$this->set_error("vx_code",_("Le code d'exemption TVA n' pas de sens sans code Facture électronique"));
}
// if tva_peppol_code is not S or Z then a VATEX code must be supplied
if ( ! in_array($this->table->tva_peppol_code??"",["Z","S"]) && trim($this->table->vx_code??"" ) =="")
{
$this->set_error("vx_code",_("Un code d'exemption de TVA doit être fourni, voyez le manuel"));
}
if ($this->count_error()!=0)
return false;
return true;