dolibarr 25.0.0-alpha
thirdparty.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2026 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2026 Nick Fragoulis
4 * Copyright (C) 2026 Frédéric France <frederic.france@free.fr>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
26require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
27require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
28
29
36{
37
44 public function __construct($db, $user)
45 {
46 $this->db = $db;
47 $this->user = $user;
48 }
49
55 public function getDefinitions(): array
56 {
57 return [
58 [
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.",
61 "inputSchema" => [
62 "type" => "object",
63 "properties" => [
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]
68 ],
69 "required" => ["query"]
70 ]
71 ],
72 [
73 "name" => "count_thirdparties",
74 "description" => "Count the number of thirdparties matching the search criteria.",
75 "inputSchema" => [
76 "type" => "object",
77 "properties" => [
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)."]
81 ],
82 "required" => ["query"]
83 ]
84 ],
85 [
86 "name" => "get_thirdparty_details",
87 "description" => "Get full details of a specific thirdparty by ID.",
88 "inputSchema" => [
89 "type" => "object",
90 "properties" => [
91 "id" => ["type" => "integer", "description" => "The unique numerical ID of the thirdparty."]
92 ],
93 "required" => ["id"]
94 ]
95 ],
96 [
97 "name" => "create_thirdparty",
98 "description" => "Create a new thirdparty: customer, prospect, or supplier (also called vendor).",
99 "inputSchema" => [
100 "type" => "object",
101 "properties" => [
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"]
116 ],
117 "required" => ["name"]
118 ]
119 ],
120 [
121 "name" => "update_thirdparty",
122 "description" => "Updates an existing thirdparty's details.",
123 "inputSchema" => [
124 "type" => "object",
125 "properties" => [
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."]
134 ],
135 "required" => ["id"]
136 ]
137 ],
138 [
139 "name" => "list_thirdparty_contacts",
140 "description" => "Lists all contacts associated with a specific thirdparty.",
141 "inputSchema" => [
142 "type" => "object",
143 "properties" => [
144 "id" => ["type" => "integer", "description" => "The ID of the thirdparty."]
145 ],
146 "required" => ["id"]
147 ]
148 ],
149 [
150 "name" => "add_thirdparty_contact",
151 "description" => "Adds a new contact to an existing thirdparty.",
152 "inputSchema" => [
153 "type" => "object",
154 "properties" => [
155 "thirdparty_identifier" => [
156 "type" => ["string", "integer"],
157 "description" => "The ID or name of the thirdparty to add the contact to."
158 ],
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."]
164 ],
165 "required" => ["thirdparty_identifier", "firstname", "lastname"]
166 ]
167 ]
168 ];
169 }
170
177 public function getRequiredRights(string $toolName)
178 {
179 $map = array(
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'))
187 );
188
189 return isset($map[$toolName]) ? $map[$toolName] : self::RIGHTS_UNDECLARED;
190 }
191
199 public function writeConfirmationPreview(string $toolName, array $args)
200 {
201 global $langs;
202
203 $langs->load("other");
204
205 switch ($toolName) {
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)));
212 default:
213 return McpTool::NO_WRITE;
214 }
215 }
216
223 public function getCategories(): array
224 {
225 return ['thirdparty', 'billing', 'commercial', 'project', 'stock'];
226 }
227
235 public function execute(string $name, array $args)
236 {
237 switch ($name) {
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': // Get info of agiven thidparty
243 return $this->getDetails($args);
244 case 'create_thirdparty':
245 return $this->create($args);
246 case 'update_thirdparty':
247 return $this->update($args);
248 case 'list_thirdparty_contacts':
249 return $this->listContacts($args);
250 case 'add_thirdparty_contact':
251 return $this->addContact($args);
252 default:
253 return ["error" => "Tool function '$name' not found."];
254 }
255 }
256
268 private function search(array $args, int $count = 0)
269 {
270 if (!$this->user->hasRight('societe', 'lire')) {
271 return ["error" => "Permission Denied"];
272 }
273
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;
278
279 // Safety fallback
280 if ($limit <= 0) {
281 $limit = 5;
282 }
283 if ($limit > 1000) {
284 dol_syslog("Search DB Error: Too many record requested", LOG_ERR);
285 return ["error" => "DB Error"];
286 }
287
288 // Dolibarr SQL construction
289 if ($count) {
290 $sql = "SELECT COUNT(s.rowid) as nb";
291 } else {
292 $sql = "SELECT s.rowid, s.nom, s.name_alias, s.code_client, s.code_fournisseur, s.email, s.client, s.fournisseur";
293 }
294 $sql .= " FROM " . MAIN_DB_PREFIX . "societe as s";
295 if ($country_code) {
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)."'";
297 }
298 $sql .= " WHERE s.entity IN (" . getEntity('societe') . ")";
299
300 if (is_numeric($query)) {
301 $sql .= " AND s.rowid = " . (int) $query;
302 $limit = 1;
303 } else {
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) . "%')";
309 }
310
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";
317 }
318
319 $sql .= " ORDER BY s.nom ASC";
320 $sql .= $this->db->plimit($limit);
321
322 $resql = $this->db->query($sql);
323
324 if (!$resql) {
325 dol_syslog("Search DB Error: " . $this->db->lasterror(), LOG_ERR);
326 return ["error" => "DB Error"];
327 }
328
329 $data = [];
330
331 while ($obj = $this->db->fetch_object($resql)) {
332 if ($count) {
333 $data = [
334 "count" => (int) $obj->nb
335 ];
336
337 $this->db->free($resql);
338
339 return $data;
340 }
341
342 $roles = [];
343
344 // Cast strictly to ensure type safety in logic
345 $is_client = (int) $obj->client;
346 $is_supplier = (int) $obj->fournisseur;
347
348 if ($is_client === 1 || $is_client === 3) {
349 $roles[] = "Customer";
350 }
351 if ($is_client === 2 || $is_client === 3) {
352 $roles[] = "Prospect";
353 }
354 if ($is_supplier === 1) {
355 $roles[] = "Supplier";
356 }
357
358 $data[] = [
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
367 ];
368 }
369
370 $this->db->free($resql);
371
372 return $data;
373 }
374
383 private function getDetails(array $args): array
384 {
385 if (!$this->user->hasRight('societe', 'lire')) {
386 return ["error" => "Permission Denied"];
387 }
388
389 if (empty($args['id'])) {
390 return ["error" => "ID is required"];
391 }
392
393 $soc = new Societe($this->db);
394
395 // Fetch returns > 0 on success, 0 on not found, < 0 on error
396 $result = $soc->fetch((int) $args['id']);
397
398 if ($result > 0) {
399 return [
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,
409 // Mode 0: the plain label. Modes 2-5 return a <span class="badge">
410 // markup that the chat renders as raw HTML (and that the model
411 // would read as content), so they must not be used here.
412 "status" => (string) $soc->getLibStatut(0),
413 "url" => DOL_URL_ROOT . "/societe/card.php?socid=" . $soc->id
414 ];
415 }
416
417 return ["error" => "Thirdparty not found"];
418 }
419
420
429 private function create(array $args)
430 {
431 // Check permissions
432 if (!$this->user->hasRight('societe', 'creer')) {
433 return ["error" => "Permission Denied"];
434 }
435
436 // Validate mandatory fields
437 if (empty($args['name'])) {
438 return ["error" => "Name is required"];
439 }
440
441 // Guardrails against low-quality model extractions.
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)."];
445 }
446
447 $warnings = array();
448 if (!empty($args['email']) && !isValidEmail((string) $args['email'])) {
449 $warnings[] = "Email '".$args['email']."' is not a valid address and was not saved.";
450 $args['email'] = '';
451 }
452
453 // Refuse silent duplicates: an identical name almost always means the
454 // same document was processed twice.
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."];
458 }
459
460 $soc = new Societe($this->db);
461
462 // Assign properties with strict casting to prevent null issues in strict mode
463 $soc->nom = $name;
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'] : '';
475
476 // Country Handling
477 if (! empty($args['country_code'])) {
478 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
479 // getCountry returns an array or false/0.
481 $info = getCountry($args['country_code'], '1');
482 if ($info && isset($info['id'])) {
483 $soc->country_id = (int) $info['id'];
484 }
485 }
486
487 // Fallback to default country if not set
488 if (empty($soc->country_id) && getDolGlobalString('MAIN_INFO_SOCIETE_COUNTRY')) {
489 $soc->country_id = getDolGlobalInt('MAIN_INFO_SOCIETE_COUNTRY');
490 }
491
492 $type = $args['type'] ?? 'customer';
493 $soc->client = 0; // Default: not a customer
494 $soc->fournisseur = 0; // Default: not a supplier
495
496 if ($type === 'customer') {
497 $soc->client = 1;
498 if (empty($soc->code_client)) {
499 $soc->code_client = '-1';
500 }
501 } elseif ($type === 'prospect') {
502 $soc->client = 2;
503 if (empty($soc->code_client)) {
504 $soc->code_client = '-1';
505 }
506 } elseif ($type === 'both') {
507 $soc->client = 3; // Prospect + Customer
508 if (empty($soc->code_client)) {
509 $soc->code_client = '-1';
510 }
511 }
512
513 if ($type === 'supplier' || $type === 'both') {
514 $soc->fournisseur = 1;
515 if (empty($soc->code_fournisseur)) {
516 $soc->code_fournisseur = '-1';
517 }
518 }
519
520 $result = $soc->create($this->user);
521
522 if ($result > 0) {
523 $out = [
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
529 ];
530 if (!empty($warnings)) {
531 $out["warnings"] = $warnings;
532 }
533
534 return $out;
535 }
536
537 return ["error" => "Create failed: " . (string) $soc->error];
538 }
539
548 private function update(array $args)
549 {
550 if (!$this->user->hasRight('societe', 'creer') && !$this->user->hasRight('societe', 'modifier')) {
551 return ["error" => "Permission Denied to update."];
552 }
553
554 if (empty($args['id'])) {
555 return ["error" => "ID is required for update."];
556 }
557
558 $soc = new Societe($this->db);
559
560 $result = $soc->fetch((int) $args['id']);
561 if ($result <= 0) {
562 return ["error" => "Thirdparty not found with ID: " . $args['id']];
563 }
564
565 // Update fields only if provided in arguments (Partial Update)
566 // We cast to (string) to ensure strict type compliance
567 if (isset($args['name'])) {
568 $soc->nom = (string) $args['name'];
569 }
570 if (isset($args['email'])) {
571 $soc->email = (string) $args['email'];
572 }
573 if (isset($args['phone'])) {
574 $soc->phone = (string) $args['phone'];
575 }
576 if (isset($args['address'])) {
577 $soc->address = (string) $args['address'];
578 }
579 if (isset($args['zip'])) {
580 $soc->zip = (string) $args['zip'];
581 }
582 if (isset($args['town'])) {
583 $soc->town = (string) $args['town'];
584 }
585
586 // Handle Country update
587 if (!empty($args['country_code'])) {
588 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
589
591 $info = getCountry($args['country_code'], '1');
592
593 if ($info && isset($info['id'])) {
594 $soc->country_id = (int) $info['id'];
595 }
596 }
597
598 if ($soc->update($soc->id, $this->user) > 0) {
599 return [
600 "status" => "success",
601 "message" => "Thirdparty updated successfully.",
602 "id" => (int) $soc->id,
603 "url" => DOL_URL_ROOT . "/societe/card.php?socid=" . $soc->id
604 ];
605 }
606
607 return ["error" => "Update failed: " . (string) $soc->error];
608 }
609
618 private function listContacts(array $args)
619 {
620 global $langs;
621
622 if (!$this->user->hasRight('societe', 'lire')) {
623 return ["error" => "Permission Denied"];
624 }
625
626 if (empty($args['id'])) {
627 return ["error" => "Thirdparty ID is required"];
628 }
629
630 $socid = (int) $args['id'];
631 $langs->load("companies");
632 $langs->load("other");
633
634 // Translations
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");
642
643 $link_text = $langs->transnoentitiesnoconv("Show");
644
645 // Verify thirdparty exists first
646 $soc = new Societe($this->db);
647 if ($soc->fetch($socid) <= 0) {
648 return ["error" => "Thirdparty not found with ID: " . $socid];
649 }
650
651 // Build SQL
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; // Fix: Added explicit (int) cast to satisfy CodingPhpTest static analysis
655 $sql .= " AND t.entity IN (" . getEntity('socpeople') . ")";
656 $sql .= " ORDER BY t.lastname ASC, t.firstname ASC";
657
658 $resql = $this->db->query($sql);
659
660 if (! $resql) {
661 dol_syslog("DB Error in listContacts: " . $this->db->lasterror(), LOG_ERR);
662 return ["error" => "DB Error"];
663 }
664
665 $data = [];
666
667 while ($obj = $this->db->fetch_object($resql)) {
668 // Absolute URL
669 $absoluteUrl = DOL_URL_ROOT . "/contact/card.php?id=" . $obj->rowid;
670
671 $htmlLink = "<a href='" . $absoluteUrl . "' target='_blank'>" . $link_text . "</a>";
672
673 $data[] = [
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
681 ];
682 }
683
684 $this->db->free($resql);
685
686 return $data;
687 }
688
698 private function addContact(array $args)
699 {
700 if (!$this->user->hasRight('societe', 'creer')) {
701 return ["error" => "Permission Denied to create contacts."];
702 }
703
704 if (empty($args['thirdparty_identifier']) || empty($args['firstname']) || empty($args['lastname'])) {
705 return ["error" => "Thirdparty identifier, firstname and lastname are required."];
706 }
707
708 $identifier = $args['thirdparty_identifier'];
709 $soc = new Societe($this->db);
710 $res = 0;
711
712 // Fetch Thirdparty
713 if (is_numeric($identifier)) {
714 // Fetch by RowID
715 $res = $soc->fetch((int) $identifier);
716 } else {
717 // Fetch by Ref/Code (2nd argument of fetch is $ref)
718 $res = $soc->fetch(0, (string) $identifier);
719 }
720
721 if ($res <= 0) {
722 return ["error" => "Thirdparty not found with identifier: " . $identifier];
723 }
724
725 // Prepare Contact
726 $contact = new Contact($this->db);
727 $contact->socid = $soc->id; // Link to the fetched thirdparty
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'] : '';
733
734 // Attempt Creation
735 if ($contact->create($this->user) > 0) {
736 return [
737 "status" => "success",
738 "message" => "Contact created successfully.",
739 "id" => (int) $contact->id,
740 "url" => DOL_URL_ROOT . "/contact/card.php?id=" . $contact->id
741 ];
742 }
743
744 return ["error" => "Failed to create contact: " . (string) $contact->error];
745 }
746}
Class to manage contact/addresses.
Abstract base class for all MCP (Model Context Protocol) tools.
const RIGHTS_UNDECLARED
Default of getRequiredRights(): the class never declared anything, deny.
const NO_WRITE
Returned by writeConfirmationPreview() when the tool writes nothing.
Class to manage third parties objects (customers, suppliers, prospects...)
Class ToolThirdParty.
getDefinitions()
Returns an array of tool definitions, including name, description, and input schema.
getDetails(array $args)
Fetch details for a specific third party (Societe).
execute(string $name, array $args)
Executes the requested tool function based on its name.
search(array $args, int $count=0)
Search for third parties based on provided criteria.
__construct($db, $user)
Constructor.
addContact(array $args)
Add a contact linked to a thirdparty.
getCategories()
Return categories this tool belongs to.
writeConfirmationPreview(string $toolName, array $args)
Preview of the third-party record this call would write.
getRequiredRights(string $toolName)
Third-party reads and writes.
listContacts(array $args)
List all contacts for a given thirdparty.
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.
Definition repair.php:141