PEPPOL: allow to verify that the customer has is on PEPPOL (Belgium only)

This commit is contained in:
sparkyx 2025-10-29 09:52:03 +01:00
parent 81623d9c6f
commit a1c59f6cf0
6 changed files with 460 additions and 4 deletions

View file

@ -45,6 +45,7 @@ $http=new HttpInput();
try {
$op= $http->request("op");
if ($op =='check_vatnumber') session_write_close();
if ($op =='search_peppol') session_write_close();
} catch (\Exception $e) {
exit();
@ -376,6 +377,8 @@ $path = array(
, 'email_setting'=>'ajax_email_setting'
// check iban number
, 'check_ibannumber'=>'ajax_check_ibannumber'
// related to peppol : search
,'search_peppol'=>'ajax_search_peppol'
) ;
if (array_key_exists($op, $path)) {

View file

@ -417,8 +417,52 @@ category_card.check_ibannumber = function (p_domid) {
console.error(e.message);
}
}
category_card.search_peppol_participant=function(p_domid) {
/**
* Display a dialog box to search the PEPPOL ID of someone
* @parameter p_domid (string) ID of the DOM Element to update
*/
category_card.display_search_peppol = function (p_domid)
{
try
{
var dgbox = "peppol_id_search_div";
waiting_box();
var queryString = {
op:'search_peppol'
,ctl:p_domid
}
if (id$("peppol_id_search_div_frm")) {
queryString['query']=$F("query");
queryString['filter']=$F("filter");
}
var action = new Ajax.Request(
"ajax_misc.php",
{
method: 'GET',
parameters: queryString,
onSuccess: function (req) {
remove_waiting_box();
if (req.responseText == 'NOCONX') {
reconnect();
return;
}
var y = calcy(15);
var div_style = "position:absolute;" + ";top:" + y + "px" + ";z-index:" + get_next_layer();
add_div({id: dgbox, cssclass: 'inner_box', html: loading(), style: div_style, drag: false});
$(dgbox).update( req.responseText);
}
}
);
} catch (e)
{
alert_box(e.message);
}
}
//-->

View file

@ -0,0 +1,217 @@
<?php
/*
* This file is part of NOALYSS.
*
* PhpCompta 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 PhpCompta; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Copyright (2016) Author Dany De Bontridder <dany@alchimerys.be>
if (!defined('ALLOWED'))
die('Appel direct ne sont pas permis');
/**
* @file
* @brief search Peppol ID.
*
* Parameters :
* - query : string to search
* - ctl : DOMID to udpate (useless)
* - filter: query on name , VAT id or Enterprise ID
*/
$http = new HttpInput();
try {
/* @var $query (string) string to search */
$query = $http->request('query', 'string', '');
/* @$ctl_id (string) DOM ID of the element to update */
$ctl_id = $http->request('ctl', 'string');
/* @filter (string) search on name, VAT ID or enterprise ID */
$filter = $http->request('filter', 'string', '');
} catch (Exception $e) {
echo $e->getMessage();
return;
}
/**
* @class
* @brief Found PEPPOL Elements, inner class
*/
class PEPPOL_Match_Record
{
var $participantID;
var array $docTypeID;
var $entity;
}
class PEPPOL_Entity_Record
{
var $name;
var $countryCode;
var $regDate;
}
$msg = _("La recherche par nom et numéro de TVA sont limitées à la Belgique");
echo \HtmlInput::title_box(_("Recherche PEPPOL Directory"), 'peppol_id_search_div');
?>
<div class="content">
<form method="GET" id="peppol_id_search_div_frm" onsubmit="category_card.display_search_peppol('<?= $ctl_id ?>');return false;">
<p class="text-muted">
<?= $msg ?>
</p>
<?php
echo \HtmlInput::hidden("op", "search_peppol");
echo \HtmlInput::hidden("ctl_id", $ctl_id);
$input_query = new IText("query", $query);
$select_filter = new ISelect("filter");
$select_filter->transform(["vatid" => _("Numéro de TVA"), "entid" => _("Numéro entreprise"), "name" => _("Nom")]);
$select_filter->selected = $filter
?>
<?= $select_filter->input() ?>
<?= $input_query->input() ?>
<ul class="aligned-block">
<li>
<?php
echo \HtmlInput::submit("search", _("Chercher"));
?>
</li>
<li>
<?php
echo \HtmlInput::button_close('peppol_id_search_div');
?>
</li>
</ul>
</form>
<?php
if (trim($query) == '')
{
echo '</div>'; // div class content
return;
}
//------------------------------------------------
// if a query has been submitted, show the result
//------------------------------------------------
$search = null;
switch ($filter)
{
case 'name':
$search = http_build_query(["name" => $query, 'countryCode' => 'BE']);
break;
case "vatid":
if (stripos("x" . $query, "BE") == 0)
{
print _("Numéro de TVA invalide: doit commencer par BE");
break;
}
$search = http_build_query(["participant" => 'iso6523-actorid-upis::9925:' . $query]);
break;
case 'entid':
if (strlen(trim($query ?? "")) != 10)
{
print _("Numéro entreprise belge valide: 10 chiffres");
break;
}
$search = http_build_query(["participant" => 'iso6523-actorid-upis::0208:' . $query]);
break;
default:
throw new \Exception("ASP129 unknown filter",129);
}
$str = file_get_contents("https://directory.peppol.eu/search/1.0/xml?" . $search);
//$str= file_get_contents("/tmp/result.xml");
$xml = new DOMDocument();
$xml->loadXML($str);
$root = $xml->getElementsByTagName("resultlist");
if (count($root) == 0)
{
echo span(_("Aucun résultat"),' class="notice" ');
echo '</div>'; // div class content
return ;
} else
{
printf(_("Résultat %d"), $root[0]->getAttribute("total-result-count"));
}
$a_match = $xml->getElementsByTagName("match");
$nb_match = $a_match->count();
$result=array();
for ($i = 0; $i < $nb_match; $i++)
{
$node = $a_match->item($i);
$obj = new PEPPOL_Match_Record();
for ($e = 0; $e < $node->childElementCount; $e++)
{
if ($node->childNodes->item($e)->nodeType != XML_ELEMENT_NODE)
{
continue;
}
if ($node->childNodes->item($e)->tagName == 'participantID')
{
$obj->participantID = $node->childNodes->item($e)->textContent;
} elseif ($node->childNodes->item($e)->tagName == 'docTypeID')
{
$obj->docTypeID[] = $node->childNodes->item($e)->textContent;
} elseif ($node->childNodes->item($e)->tagName == 'entity')
{
$obj->entity = new PEPPOL_Entity_Record();
for ($f=0;$f< $node->childNodes->item($e)->childElementCount;$f++)
{
if ( $node->childNodes->item($e)->childNodes->item($f)->tagName == 'name')
$obj->entity->name=$node->childNodes->item($e)->childNodes->item($f)->textContent;
if ($node->childNodes->item($e)->childNodes->item($f)->tagName=='countryCode')
$obj->entity->countryCode=$node->childNodes->item($e)->childNodes->item($f)->textContent;
if ($node->childNodes->item($e)->childNodes->item($f)->tagName=='regDate')
$obj->entity->regDate=$node->childNodes->item($e)->childNodes->item($f)->textContent;
}
}
}
if ($obj->participantID != null) $result[]=clone $obj;
}
if (empty($result))
{
echo span(_("Aucun résultat"),' class="notice" ');
return;
}
//------------------------------------------------
// display result
//------------------------------------------------
$nb_result=count($result);
?>
<?php
for ($i=0;$i < $nb_result;$i++)
{
?>
<div style="display:flex;align-content: space-evenly">
<div style="width:20rem;">
<a href="javascript:void(0)" onclick="$('<?=$ctl?>').value='<?=$result[$i]->participantID?>';removeDiv('peppol_id_search_div')">
<?=$result[$i]->participantID?>
</a>
</div>
<div>
<?=$result[$i]->entity->name?>
</div>
</div>
<?php
} // end for ($i)
echo '</div>'; // div class content
?>

View file

@ -217,7 +217,10 @@ class Card_Property
}
if ( $this->ad_id == ATTR_DEF_PEPPOLID)
{
/// Propose a button to check VAT
$result['input']=new IPEPPOL_ID( "av_text" . $this->ad_id,$this->av_text);
$result['label']=$this->ad_text;
return $result;
}
if ($this->ad_id == ATTR_DEF_TVA) {

View file

@ -0,0 +1,86 @@
<?php
/*
* 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 13/05/24
/*!
* \file
* \brief input of the VAT Number of a supplier / customer, propose a button to check VAT
*/
class IPEPPOL_ID extends HtmlInput
{
private $itext;
function __construct($name = '', $value = '', $p_id = "")
{
$p_id = ($p_id=="")?uniqid("peppol_id"):$p_id;
$this->itext = new IText($name,$value,$p_id);
$this->itext->title = _("PEPPOL_ID");
$this->itext->placeholder = "9999:9999999999";
$this->itext->extra = "";
$this->itext->style = ' class="input_text" ';
$this->autofocus = false;
}
function input()
{
if ( $this->readOnly==true) return $this->display();
$return = $this->itext->input();
$return .= $this->button_check_peppol();
$return.=sprintf('<div id="info%s" class="notice" style="margin:0px;font-size:80%%;width:auto"></div>',$this->itext->id);
return $return;
}
public function getIText(): IText
{
return $this->itext;
}
public function setIText(IText $itext): IPEPPOL_ID
{
$this->itext = $itext;
return $this;
}
function button_check_peppol()
{
$button=new \IButton(uniqid());
$button->javascript=sprintf("category_card.display_search_peppol('%s')",
$this->itext->id);
$button->extra='style="padding-bottom:0px"';
$button->label=_("Vérifie");
return $button->input();
}
function display()
{
return $this->itext->display();
}
static function testme()
{
$ivatnumber=new IPEPPOL_ID("av_text13");
echo $ivatnumber->input();
}
}

View file

@ -16,7 +16,7 @@ update "parameter" set pr_id='MY_POSTCODE' where pr_id='MY_CP';
update "parameter" set pr_id='MY_CITY' where pr_id='MY_COMMUNE';
update "parameter" set pr_id='MY_COUNTRY' where pr_id='MY_PAYS';
insert into attr_def (ad_id,ad_text,ad_type,ad_size,ad_search_followup,ad_default_order) values(55,'SIRENE','text',20,1,14);
insert into attr_def (ad_id,ad_text,ad_type,ad_size,ad_search_followup,ad_default_order) values(55,'SIREN','text',20,1,14);
insert into attr_def (ad_id,ad_text,ad_type,ad_size,ad_search_followup,ad_default_order) values(56,'SIRET','text',20,1,15);
insert into attr_def (ad_id,ad_text,ad_type,ad_size,ad_search_followup,ad_default_order) values(58,'PEPPOL ID','text',20,1,15);
@ -369,3 +369,106 @@ insert into profile_menu
select me_code,'CFG',1,2,'E',0,(select distinct m2.pm_id from profile_menu m2 where m2.me_code='CFG' limit 1) from menu_ref where me_code='C0ML';
create table parameter_internal (pi_id text not null primary key, pi_value text);
comment on table parameter_internal is 'Internal parameter, used by the application , it can''t not be changed by the interface';
create or replace function comptaproc.fill_internal_parameter()
returns text
as
$$
declare
str_country text;
n_found int;
begin
select lower(pr_value) into str_country from "parameter" where pr_id = 'MY_COUNTRY';
if str_country = 'belgique' or str_country = 'be' then
insert into parameter_internal values ('COUNTRY_CODE','BE') on conflict do nothing;
return 'BE';
end if ;
if str_country = 'france' or str_country = 'fr' then
insert into parameter_internal values ('COUNTRY_CODE','FR') on conflict do nothing;
return 'FR';
end if ;
select 1 into n_found from bilan where b_file_template='document/fr_fr/fr_plan_abrege_perso_cr1000.form';
if n_found is NULL then
insert into parameter_internal values ('COUNTRY_CODE','BE') on conflict do nothing;
return 'BE';
end if;
insert into parameter_internal values ('COUNTRY_CODE','FR') on conflict do nothing;
return 'FR';
end;
$$
language plpgsql;
select comptaproc.fill_internal_parameter();
-- insert new attributes for PEPPOL, SIREN and QUANTYTI
insert into jnt_fic_attr (fd_id,ad_id,jnt_order)
select f1.fd_id , 58,15
from fiche_def f1
join fiche_def f2 on (f1.fd_id=f2.fd_id)
where f2.frd_id in (8,9)
on conflict do nothing;
insert into jnt_fic_attr (fd_id,ad_id,jnt_order)
select f1.fd_id , 57,16
from fiche_def f1
join fiche_def f2 on (f1.fd_id=f2.fd_id)
where f2.frd_id in (8,9)
on conflict do nothing;
insert into jnt_fic_attr (fd_id,ad_id,jnt_order)
select f1.fd_id , 55,16
from fiche_def f1
join fiche_def f2 on (f1.fd_id=f2.fd_id)
where f2.frd_id in (8,9)
on conflict do nothing;
insert into jnt_fic_attr (fd_id,ad_id,jnt_order)
select f1.fd_id , 59,90
from fiche_def f1
join fiche_def f2 on (f1.fd_id=f2.fd_id)
where f2.frd_id in (1,2)
on conflict do nothing;
-- Add a attribute PEPPOL ID for customer and supplier in Belgium only
-- for the card category
-- Plus, compute a default value
--
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
$BODY$
declare
/*
* Section for variables
*/
n_card_id int;
vat_number text;
record_card RECORD;
result_fct int;
begin
result_fct := 0;
for record_card in select * from fiche_detail where ad_id=13 and LOWER (ad_value ) like 'be%'
loop
vat_number := regexp_replace(record_card.ad_value,'\D','','g');
if length(vat_number) = 10 then
update fiche_detail set ad_value = '0208:'||vat_number where f_id=record_card.f_id and ad_id=58;
RESULT_fct := result_fct + 1;
end if;
end loop;
return result_fct;
end;
$BODY$
LANGUAGE plpgsql;
select comptaproc.update_peppol();