26require_once DOL_DOCUMENT_ROOT .
'/societe/class/societe.class.php';
27require_once DOL_DOCUMENT_ROOT .
'/contact/class/contact.class.php';
59 "name" =>
"search_thirdparties",
60 "description" =>
"Search for a thirdparty by ID, name, alias, code, or email. If a numerical ID is provided alone, it returns an exact match. If a name is provided, it returns a list of matches.",
64 "query" => [
"type" => [
"string",
"integer"],
"description" =>
"The ID of the thirdparty, or a name/alias/code/email to search for."],
65 "type" => [
"type" =>
"string",
"enum" => [
"customer",
"prospect",
"supplier"],
"description" =>
"Filter by type (optional)."],
66 "country_code" => [
"type" =>
"string",
"description" =>
"ISO 2-letter country code (e.g. US, FR, GR) (optional)."],
67 "limit" => [
"type" =>
"integer",
"default" => 5]
69 "required" => [
"query"]
73 "name" =>
"count_thirdparties",
74 "description" =>
"Count the number of thirdparties matching the search criteria.",
78 "query" => [
"type" => [
"string",
"integer"],
"description" =>
"A name/alias/code/email to search for."],
79 "type" => [
"type" =>
"string",
"enum" => [
"customer",
"prospect",
"supplier"],
"description" =>
"Filter by type (optional)."],
80 "country_code" => [
"type" =>
"string",
"description" =>
"ISO 2-letter country code (e.g. US, FR, GR) (optional)."]
82 "required" => [
"query"]
86 "name" =>
"get_thirdparty_details",
87 "description" =>
"Get full details of a specific thirdparty by ID.",
91 "id" => [
"type" =>
"integer",
"description" =>
"The unique numerical ID of the thirdparty."]
97 "name" =>
"create_thirdparty",
98 "description" =>
"Create a new thirdparty: customer, prospect, or supplier (also called vendor).",
102 "name" => [
"type" =>
"string",
"description" =>
"Name of the thirdparty"],
103 "type" => [
"type" =>
"string",
"enum" => [
"customer",
"prospect",
"supplier",
"both",
"none"],
"default" =>
"customer",
"description" =>
"Type of thirdparty. Use 'supplier' for a vendor. 'none' means not a customer or prospect."],
104 "email" => [
"type" =>
"string",
"description" =>
"Email address"],
105 "phone" => [
"type" =>
"string",
"description" =>
"Phone number"],
106 "address" => [
"type" =>
"string",
"description" =>
"Address"],
107 "zip" => [
"type" =>
"string",
"description" =>
"Postal code"],
108 "town" => [
"type" =>
"string",
"description" =>
"Town/City"],
109 "country_code" => [
"type" =>
"string",
"description" =>
"ISO 2-letter country code (e.g. US, FR, GR)"],
110 "code_client" => [
"type" =>
"string",
"description" =>
"Customer code (optional, -1 for auto-generation)"],
111 "tva_intra" => [
"type" =>
"string",
"description" =>
"VAT number. For EU companies use the intra-community format with country prefix (e.g. FR12345678901, DE123456789, EL123456789)."],
112 "idprof1" => [
"type" =>
"string",
"description" =>
"Professional ID 1 - country-specific (e.g. SIREN in France, CIF in Spain, AFM in Greece, Company Number in UK)"],
113 "idprof2" => [
"type" =>
"string",
"description" =>
"Professional ID 2"],
114 "idprof3" => [
"type" =>
"string",
"description" =>
"Professional ID 3"],
115 "idprof4" => [
"type" =>
"string",
"description" =>
"Professional ID 4"]
117 "required" => [
"name"]
121 "name" =>
"update_thirdparty",
122 "description" =>
"Updates an existing thirdparty's details.",
126 "id" => [
"type" =>
"integer",
"description" =>
"The ID of the thirdparty to update."],
127 "name" => [
"type" =>
"string",
"description" =>
"The new name for the thirdparty."],
128 "email" => [
"type" =>
"string",
"description" =>
"The new email address."],
129 "phone" => [
"type" =>
"string",
"description" =>
"The new phone number."],
130 "address" => [
"type" =>
"string",
"description" =>
"The new address."],
131 "zip" => [
"type" =>
"string",
"description" =>
"The new postal code."],
132 "town" => [
"type" =>
"string",
"description" =>
"The new town."],
133 "country_code" => [
"type" =>
"string",
"description" =>
"The new ISO 2-letter country code."]
139 "name" =>
"list_thirdparty_contacts",
140 "description" =>
"Lists all contacts associated with a specific thirdparty.",
144 "id" => [
"type" =>
"integer",
"description" =>
"The ID of the thirdparty."]
150 "name" =>
"add_thirdparty_contact",
151 "description" =>
"Adds a new contact to an existing thirdparty.",
155 "thirdparty_identifier" => [
156 "type" => [
"string",
"integer"],
157 "description" =>
"The ID or name of the thirdparty to add the contact to."
159 "firstname" => [
"type" =>
"string",
"description" =>
"Contact's first name."],
160 "lastname" => [
"type" =>
"string",
"description" =>
"Contact's last name."],
161 "email" => [
"type" =>
"string",
"description" =>
"Contact's email address."],
162 "phone" => [
"type" =>
"string",
"description" =>
"Contact's phone number."],
163 "role" => [
"type" =>
"string",
"description" =>
"Contact's role or position within the company."]
165 "required" => [
"thirdparty_identifier",
"firstname",
"lastname"]
180 'search_thirdparties' => array(array(
'societe',
'lire')),
181 'count_thirdparties' => array(array(
'societe',
'lire')),
182 'get_thirdparty_details' => array(array(
'societe',
'lire')),
183 'list_thirdparty_contacts' => array(array(
'societe',
'contact',
'lire')),
184 'create_thirdparty' => array(array(
'societe',
'creer')),
185 'update_thirdparty' => array(array(
'societe',
'creer')),
186 'add_thirdparty_contact' => array(array(
'societe',
'contact',
'creer'))
203 $langs->load(
"other");
206 case 'create_thirdparty':
207 return $langs->trans(
"AIPreviewCreateThirdparty", (
string) ($args[
'name'] ??
''));
208 case 'update_thirdparty':
209 return $langs->trans(
"AIPreviewUpdateThirdparty", (
string) ((
int) ($args[
'id'] ?? 0)), implode(
', ', array_keys(array_diff_key($args, array(
'id' => 1)))));
210 case 'add_thirdparty_contact':
211 return $langs->trans(
"AIPreviewAddContact", trim(((
string) ($args[
'firstname'] ??
'')).
' '.((
string) ($args[
'lastname'] ??
''))), (
string) ((
int) ($args[
'socid'] ?? 0)));
225 return [
'thirdparty',
'billing',
'commercial',
'project',
'stock'];
235 public function execute(
string $name, array $args)
238 case 'search_thirdparties':
239 return $this->
search($args, 0);
240 case 'count_thirdparties':
241 return $this->
search($args, 1);
242 case 'get_thirdparty_details':
244 case 'create_thirdparty':
245 return $this->create($args);
246 case 'update_thirdparty':
247 return $this->update($args);
248 case 'list_thirdparty_contacts':
250 case 'add_thirdparty_contact':
253 return [
"error" =>
"Tool function '$name' not found."];
268 private function search(array $args,
int $count = 0)
270 if (!$this->
user->hasRight(
'societe',
'lire')) {
271 return [
"error" =>
"Permission Denied"];
274 $query = $args[
'query'];
275 $type = isset($args[
'type']) ? (string) $args[
'type'] :
'';
276 $country_code = isset($args[
'country_code']) ? (string) $args[
'country_code'] :
'';
277 $limit = isset($args[
'limit']) ? (int) $args[
'limit'] : 5;
284 dol_syslog(
"Search DB Error: Too many record requested", LOG_ERR);
285 return [
"error" =>
"DB Error"];
290 $sql =
"SELECT COUNT(s.rowid) as nb";
292 $sql =
"SELECT s.rowid, s.nom, s.name_alias, s.code_client, s.code_fournisseur, s.email, s.client, s.fournisseur";
294 $sql .=
" FROM " . MAIN_DB_PREFIX .
"societe as s";
296 $sql.=
" INNER JOIN ".MAIN_DB_PREFIX.
"c_country as c ON s.fk_pays = c.rowid AND c.code = '".$this->db->escape($country_code).
"'";
298 $sql .=
" WHERE s.entity IN (" .
getEntity(
'societe') .
")";
300 if (is_numeric($query)) {
301 $sql .=
" AND s.rowid = " . (int) $query;
304 $sql .=
" AND (s.nom LIKE '%" . $this->db->escape($query) .
"%'";
305 $sql .=
" OR s.name_alias LIKE '%" . $this->db->escape($query) .
"%'";
306 $sql .=
" OR s.code_client LIKE '%" . $this->db->escape($query) .
"%'";
307 $sql .=
" OR s.code_fournisseur LIKE '%" . $this->db->escape($query) .
"%'";
308 $sql .=
" OR s.email LIKE '%" . $this->db->escape($query) .
"%')";
311 if ($type ===
'customer') {
312 $sql .=
" AND s.client IN (1, 3)";
313 } elseif ($type ===
'prospect') {
314 $sql .=
" AND s.client IN (2, 3)";
315 } elseif ($type ===
'supplier') {
316 $sql .=
" AND s.fournisseur = 1";
319 $sql .=
" ORDER BY s.nom ASC";
320 $sql .= $this->db->plimit($limit);
322 $resql = $this->db->query($sql);
325 dol_syslog(
"Search DB Error: " . $this->db->lasterror(), LOG_ERR);
326 return [
"error" =>
"DB Error"];
331 while ($obj = $this->db->fetch_object($resql)) {
334 "count" => (int) $obj->nb
337 $this->db->free($resql);
345 $is_client = (int) $obj->client;
346 $is_supplier = (int) $obj->fournisseur;
348 if ($is_client === 1 || $is_client === 3) {
349 $roles[] =
"Customer";
351 if ($is_client === 2 || $is_client === 3) {
352 $roles[] =
"Prospect";
354 if ($is_supplier === 1) {
355 $roles[] =
"Supplier";
359 "id" => (int) $obj->rowid,
360 "name" => (
string) $obj->nom,
361 "alias" => (string) $obj->name_alias,
362 "code_cust" => (
string) $obj->code_client,
363 "code_sup" => (string) $obj->code_fournisseur,
364 "email" => (
string) $obj->email,
365 "type" => implode(
'/', $roles),
366 "url" => DOL_URL_ROOT .
"/societe/card.php?socid=" . $obj->rowid
370 $this->db->free($resql);
385 if (!$this->
user->hasRight(
'societe',
'lire')) {
386 return [
"error" =>
"Permission Denied"];
389 if (empty($args[
'id'])) {
390 return [
"error" =>
"ID is required"];
396 $result = $soc->fetch((
int) $args[
'id']);
400 "id" => (int) $soc->id,
401 "name" => (
string) $soc->nom,
402 "address" => (string) $soc->address,
403 "zip" => (
string) $soc->zip,
404 "city" => (string) $soc->town,
405 "country" => (
string) $soc->country,
406 "email" => (string) $soc->email,
407 "phone" => (
string) $soc->phone,
408 "vat" => (string) $soc->tva_intra,
412 "status" => (
string) $soc->getLibStatut(0),
413 "url" => DOL_URL_ROOT .
"/societe/card.php?socid=" . $soc->id
417 return [
"error" =>
"Thirdparty not found"];
429 private function create(array $args)
432 if (!$this->
user->hasRight(
'societe',
'creer')) {
433 return [
"error" =>
"Permission Denied"];
437 if (empty($args[
'name'])) {
438 return [
"error" =>
"Name is required"];
442 $name = trim((
string) $args[
'name']);
443 if (preg_match(
'/^(?:Ο\.?Ε\.?|Ε\.?Ε\.?|Ε\.?Π\.?Ε\.?|Ι\.?Κ\.?Ε\.?|Α\.?Ε\.?|O\.?E\.?|E\.?E\.?|A\.?E\.?|LTD|GMBH|S\.?A\.?|SARL|LLC|INC)$/iu', $name)) {
444 return [
"error" =>
"'".$name.
"' is only a legal-form suffix, not a company name. Extract the full company name from the document (the suffix usually follows it)."];
448 if (!empty($args[
'email']) && !
isValidEmail((
string) $args[
'email'])) {
449 $warnings[] =
"Email '".$args[
'email'].
"' is not a valid address and was not saved.";
455 $existing =
new Societe($this->db);
456 if ($existing->fetch(0, $name) > 0) {
457 return [
"error" =>
"A thirdparty named '".$name.
"' already exists (id ".$existing->id.
"). Use update_thirdparty to modify it instead of creating a duplicate."];
464 $soc->email = isset($args[
'email']) ? (string) $args[
'email'] :
'';
465 $soc->phone = isset($args[
'phone']) ? (string) $args[
'phone'] :
'';
466 $soc->address = isset($args[
'address']) ? (string) $args[
'address'] :
'';
467 $soc->zip = isset($args[
'zip']) ? (string) $args[
'zip'] :
'';
468 $soc->town = isset($args[
'town']) ? (string) $args[
'town'] :
'';
469 $soc->code_client = isset($args[
'code_client']) ? (string) $args[
'code_client'] :
'';
470 $soc->idprof1 = isset($args[
'idprof1']) ? (string) $args[
'idprof1'] :
'';
471 $soc->idprof2 = isset($args[
'idprof2']) ? (string) $args[
'idprof2'] :
'';
472 $soc->idprof3 = isset($args[
'idprof3']) ? (string) $args[
'idprof3'] :
'';
473 $soc->idprof4 = isset($args[
'idprof4']) ? (string) $args[
'idprof4'] :
'';
474 $soc->tva_intra = isset($args[
'tva_intra']) ? (string) $args[
'tva_intra'] :
'';
477 if (! empty($args[
'country_code'])) {
478 require_once DOL_DOCUMENT_ROOT .
'/core/lib/company.lib.php';
481 $info =
getCountry($args[
'country_code'],
'1');
482 if ($info && isset($info[
'id'])) {
483 $soc->country_id = (int) $info[
'id'];
492 $type = $args[
'type'] ??
'customer';
494 $soc->fournisseur = 0;
496 if ($type ===
'customer') {
498 if (empty($soc->code_client)) {
499 $soc->code_client =
'-1';
501 } elseif ($type ===
'prospect') {
503 if (empty($soc->code_client)) {
504 $soc->code_client =
'-1';
506 } elseif ($type ===
'both') {
508 if (empty($soc->code_client)) {
509 $soc->code_client =
'-1';
513 if ($type ===
'supplier' || $type ===
'both') {
514 $soc->fournisseur = 1;
515 if (empty($soc->code_fournisseur)) {
516 $soc->code_fournisseur =
'-1';
520 $result = $soc->create($this->
user);
524 "status" =>
"success",
525 "message" =>
"Thirdparty created",
526 "id" => (int) $soc->id,
527 "name" => (
string) $soc->name,
528 "url" => DOL_URL_ROOT .
"/societe/card.php?socid=" . $soc->id
530 if (!empty($warnings)) {
531 $out[
"warnings"] = $warnings;
537 return [
"error" =>
"Create failed: " . (string) $soc->error];
548 private function update(array $args)
550 if (!$this->
user->hasRight(
'societe',
'creer') && !$this->user->hasRight(
'societe',
'modifier')) {
551 return [
"error" =>
"Permission Denied to update."];
554 if (empty($args[
'id'])) {
555 return [
"error" =>
"ID is required for update."];
560 $result = $soc->fetch((
int) $args[
'id']);
562 return [
"error" =>
"Thirdparty not found with ID: " . $args[
'id']];
567 if (isset($args[
'name'])) {
568 $soc->nom = (string) $args[
'name'];
570 if (isset($args[
'email'])) {
571 $soc->email = (string) $args[
'email'];
573 if (isset($args[
'phone'])) {
574 $soc->phone = (string) $args[
'phone'];
576 if (isset($args[
'address'])) {
577 $soc->address = (string) $args[
'address'];
579 if (isset($args[
'zip'])) {
580 $soc->zip = (string) $args[
'zip'];
582 if (isset($args[
'town'])) {
583 $soc->town = (string) $args[
'town'];
587 if (!empty($args[
'country_code'])) {
588 require_once DOL_DOCUMENT_ROOT .
'/core/lib/company.lib.php';
591 $info =
getCountry($args[
'country_code'],
'1');
593 if ($info && isset($info[
'id'])) {
594 $soc->country_id = (int) $info[
'id'];
598 if ($soc->update($soc->id, $this->user) > 0) {
600 "status" =>
"success",
601 "message" =>
"Thirdparty updated successfully.",
602 "id" => (int) $soc->id,
603 "url" => DOL_URL_ROOT .
"/societe/card.php?socid=" . $soc->id
607 return [
"error" =>
"Update failed: " . (string) $soc->error];
622 if (!$this->
user->hasRight(
'societe',
'lire')) {
623 return [
"error" =>
"Permission Denied"];
626 if (empty($args[
'id'])) {
627 return [
"error" =>
"Thirdparty ID is required"];
630 $socid = (int) $args[
'id'];
631 $langs->load(
"companies");
632 $langs->load(
"other");
635 $label_id = $langs->transnoentitiesnoconv(
"Id");
636 $label_firstname = $langs->transnoentitiesnoconv(
"Firstname");
637 $label_lastname = $langs->transnoentitiesnoconv(
"Lastname");
638 $label_email = $langs->transnoentitiesnoconv(
"Email");
639 $label_phone = $langs->transnoentitiesnoconv(
"Phone");
640 $label_role = $langs->transnoentitiesnoconv(
"PostOrFunction");
641 $label_link = $langs->transnoentitiesnoconv(
"Link");
643 $link_text = $langs->transnoentitiesnoconv(
"Show");
647 if ($soc->fetch($socid) <= 0) {
648 return [
"error" =>
"Thirdparty not found with ID: " . $socid];
652 $sql =
"SELECT t.rowid, t.firstname, t.lastname, t.email, t.phone, t.poste";
653 $sql .=
" FROM " . MAIN_DB_PREFIX .
"socpeople as t";
654 $sql .=
" WHERE t.fk_soc = " . (int) $socid;
655 $sql .=
" AND t.entity IN (" .
getEntity(
'socpeople') .
")";
656 $sql .=
" ORDER BY t.lastname ASC, t.firstname ASC";
658 $resql = $this->db->query($sql);
661 dol_syslog(
"DB Error in listContacts: " . $this->db->lasterror(), LOG_ERR);
662 return [
"error" =>
"DB Error"];
667 while ($obj = $this->db->fetch_object($resql)) {
669 $absoluteUrl = DOL_URL_ROOT .
"/contact/card.php?id=" . $obj->rowid;
671 $htmlLink =
"<a href='" . $absoluteUrl .
"' target='_blank'>" . $link_text .
"</a>";
674 $label_id => (int) $obj->rowid,
675 $label_firstname => (
string) $obj->firstname,
676 $label_lastname => (string) $obj->lastname,
677 $label_email => (
string) $obj->email,
678 $label_phone => (string) $obj->phone,
679 $label_role => (
string) $obj->poste,
680 $label_link => $htmlLink
684 $this->db->free($resql);
700 if (!$this->
user->hasRight(
'societe',
'creer')) {
701 return [
"error" =>
"Permission Denied to create contacts."];
704 if (empty($args[
'thirdparty_identifier']) || empty($args[
'firstname']) || empty($args[
'lastname'])) {
705 return [
"error" =>
"Thirdparty identifier, firstname and lastname are required."];
708 $identifier = $args[
'thirdparty_identifier'];
713 if (is_numeric($identifier)) {
715 $res = $soc->fetch((
int) $identifier);
718 $res = $soc->fetch(0, (
string) $identifier);
722 return [
"error" =>
"Thirdparty not found with identifier: " . $identifier];
726 $contact =
new Contact($this->db);
727 $contact->socid = $soc->id;
728 $contact->firstname = (string) ($args[
'firstname'] ??
'');
729 $contact->lastname = (string) $args[
'lastname'];
730 $contact->email = isset($args[
'email']) ? (string) $args[
'email'] :
'';
731 $contact->phone_pro = isset($args[
'phone']) ? (string) $args[
'phone'] :
'';
732 $contact->poste = isset($args[
'role']) ? (string) $args[
'role'] :
'';
735 if ($contact->create($this->user) > 0) {
737 "status" =>
"success",
738 "message" =>
"Contact created successfully.",
739 "id" => (int) $contact->id,
740 "url" => DOL_URL_ROOT .
"/contact/card.php?id=" . $contact->id
744 return [
"error" =>
"Failed to create contact: " . (string) $contact->error];
Class to manage third parties objects (customers, suppliers, prospects...)
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
isValidEmail($address, $acceptsupervisorkey=0, $acceptuserkey=0)
Return true if email syntax is ok.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
$conf db user
Active Directory does not allow anonymous connections.