26require_once DOL_DOCUMENT_ROOT .
'/compta/facture/class/facture.class.php';
27require_once DOL_DOCUMENT_ROOT .
'/societe/class/societe.class.php';
28require_once DOL_DOCUMENT_ROOT .
'/product/class/product.class.php';
29require_once DOL_DOCUMENT_ROOT .
'/compta/paiement/class/paiement.class.php';
30require_once DOL_DOCUMENT_ROOT .
'/compta/bank/class/account.class.php';
31require_once DOL_DOCUMENT_ROOT .
'/core/lib/date.lib.php';
69 if (is_object($this->
user) && !empty($this->
user->id)) {
73 return is_object($user) && !empty($user->id) ? $user :
null;
85 "name" =>
"search_invoice",
86 "description" =>
"Search for invoices. By default, lists UNPAID invoices. Excludes drafts.",
92 "description" =>
"Optional: Customer name."
96 "enum" => [
"unpaid",
"paid",
"draft",
"all"],
97 "description" =>
"Filter by status. Default is 'unpaid'. 'all' shows history but excludes drafts.",
106 "items" => [
"type" =>
"integer"],
107 "description" =>
"Restrict to these invoice ids (e.g. the rows the user selected on a list). The result then includes count and total sums."
113 "name" =>
"get_invoice",
114 "description" =>
"Get details of a specific invoice by ID or Reference.",
118 "ref" => [
"type" =>
"string",
"description" =>
"Invoice Ref (e.g. FA2401-001)"],
119 "id" => [
"type" =>
"integer",
"description" =>
"Invoice ID"]
122 [
"required" => [
"ref"]],
123 [
"required" => [
"id"]]
128 "name" =>
"search_supplier_invoice",
129 "description" =>
"Search SUPPLIER (vendor/purchase) invoices by supplier name, ref, or payment status. Use for supplier invoices only; for customer invoices use search_invoice.",
133 "query" => [
"type" =>
"string",
"description" =>
"Supplier name, invoice ref or supplier's own ref (optional)"],
134 "status" => [
"type" =>
"string",
"enum" => [
"draft",
"unpaid",
"paid"],
"description" =>
"Filter by status (optional)"],
135 "limit" => [
"type" =>
"integer",
"default" => 10],
138 "items" => [
"type" =>
"integer"],
139 "description" =>
"Restrict to these supplier invoice ids (e.g. the rows the user selected on a list). The result then includes count and total sums."
145 "name" =>
"get_supplier_invoice",
146 "description" =>
"Get details of a specific SUPPLIER (vendor/purchase) invoice by ID or Reference. For customer invoices use get_invoice.",
150 "ref" => [
"type" =>
"string",
"description" =>
"Supplier invoice Ref"],
151 "id" => [
"type" =>
"integer",
"description" =>
"Supplier invoice ID"]
154 [
"required" => [
"ref"]],
155 [
"required" => [
"id"]]
160 "name" =>
"validate_invoice",
161 "description" =>
"Validate a draft invoice.",
165 "invoice" => [
"type" =>
"string",
"description" =>
"Invoice ID or Ref."]
167 "required" => [
"invoice"]
171 "name" =>
"pay_invoice",
172 "description" =>
"Register a payment for an invoice.",
176 "invoice" => [
"type" =>
"string",
"description" =>
"Invoice ID or Reference."],
177 "amount" => [
"type" =>
"number",
"description" =>
"Amount to pay. Defaults to full remaining."],
178 "payment_mode" => [
"type" =>
"string",
"description" =>
"Code (VIR, CB, LIQ)."],
179 "bank_account" => [
"type" =>
"string",
"description" =>
"Bank Account Name/Ref."]
181 "required" => [
"invoice"]
196 'search_invoice' => array(array(
'facture',
'lire')),
197 'search_invoices' => array(array(
'facture',
'lire')),
198 'get_invoice' => array(array(
'facture',
'lire')),
199 'validate_invoice' => array(array(
'facture',
'creer')),
200 'pay_invoice' => array(array(
'facture',
'paiement')),
201 'search_supplier_invoice' => array(array(
'fournisseur',
'facture',
'lire')),
202 'get_supplier_invoice' => array(array(
'fournisseur',
'facture',
'lire'))
219 $langs->load(
"other");
222 case 'validate_invoice':
223 return $langs->trans(
"AIPreviewValidateInvoice", (
string) ($args[
'ref'] ?? $args[
'id'] ??
''));
225 return $langs->trans(
"AIPreviewPayInvoice",
price((
float) ($args[
'amount'] ?? 0)), (
string) ($args[
'ref'] ?? $args[
'id'] ??
''));
249 public function execute(
string $name, array $args)
252 case 'search_invoice':
253 case 'search_invoices':
256 case 'search_supplier_invoice':
258 case 'get_supplier_invoice':
263 case 'validate_invoice':
270 return [
"error" =>
"Tool function '$name' not found."];
283 $limit = isset($args[
'limit']) ? (int) $args[
'limit'] : 10;
284 $status = isset($args[
'status']) ? $args[
'status'] :
'unpaid';
291 dol_syslog(
"Search DB Error: Too many record requested", LOG_ERR);
292 return [
"error" =>
"DB Error"];
295 $sql =
"SELECT f.rowid, f.ref, f.total_ht, f.total_ttc, f.fk_statut, f.paye, f.datef, s.nom
296 FROM " . MAIN_DB_PREFIX .
"facture as f
297 LEFT JOIN " . MAIN_DB_PREFIX .
"societe as s ON f.fk_soc = s.rowid
298 WHERE f.entity IN (" .
getEntity(
'facture') .
")";
306 if (!empty($args[
'ids']) && is_array($args[
'ids'])) {
307 foreach ($args[
'ids'] as $selId) {
308 if ((
int) $selId > 0 && count($onlyIds) < 100) {
309 $onlyIds[] = (int) $selId;
312 if (!empty($onlyIds)) {
313 $sql .=
" AND f.rowid IN (".$this->db->sanitize(implode(
',', $onlyIds)).
")";
319 if ($status ===
'ids') {
321 } elseif ($status ===
'draft') {
323 $sql .=
" AND f.fk_statut = 0";
324 } elseif ($status ===
'paid') {
326 $sql .=
" AND f.fk_statut = 2";
327 } elseif ($status ===
'all') {
329 $sql .=
" AND f.fk_statut IN (1, 2)";
333 $sql .=
" AND f.fk_statut = 1 AND f.paye = 0";
337 if (!empty($args[
'customer'])) {
339 if (!is_array($cust)) {
340 $sql .=
" AND f.fk_soc = " . ((int) $cust->id);
342 $sql .=
" AND s.nom LIKE '%" . $this->db->escape($args[
'customer']) .
"%'";
346 $sql .=
" ORDER BY f.datef DESC LIMIT " . ((int) $limit);
348 $resql = $this->db->query($sql);
354 while ($r = $this->db->fetch_object($resql)) {
356 if (!in_array($status, array(
'draft',
'ids')) && $r->fk_statut == 0) {
360 $ref = ($r->fk_statut == 0 ?
"(PROV" . $r->rowid .
")" : $r->ref);
363 $statusLabel =
"Unknown";
364 if ($r->fk_statut == 0) {
365 $statusLabel =
"Draft";
366 } elseif ($r->fk_statut == 1) {
367 $statusLabel =
"Unpaid";
368 } elseif ($r->fk_statut == 2) {
369 $statusLabel =
"Paid";
370 } elseif ($r->fk_statut == 3) {
371 $statusLabel =
"Abandoned";
374 $sumHt += (float) $r->total_ht;
375 $sumTtc += (float) $r->total_ttc;
379 "customer" => $r->nom,
380 "amount" =>
price($r->total_ttc),
381 "status" => $statusLabel,
382 "url" => DOL_URL_ROOT .
"/compta/facture/card.php?id=" . $r->rowid
385 $this->db->free($resql);
389 return [
"info" =>
"No " . $status .
" invoices found matching your criteria."];
392 if ($status ===
'ids') {
396 "count" => count($list),
397 "total_ht" =>
price($sumHt),
398 "total_ttc" =>
price($sumTtc),
415 $id = isset($args[
'ref']) ? $args[
'ref'] : (isset($args[
'id']) ? $args[
'id'] :
null);
418 if (is_array($invoice)) {
422 $invoice->fetch_thirdparty();
423 $invoice->fetch_lines();
426 foreach ($invoice->lines as $l) {
427 $prodRef = !empty($l->product_ref) ? $l->product_ref : (!empty($l->product_label) ? $l->product_label :
'');
430 "product" => $prodRef,
432 "qty" => (float) $l->qty,
433 "price" =>
price($l->subprice),
434 "total_line" =>
price($l->total_ht),
435 "vat" => $l->tva_tx .
"%"
440 "id" => $invoice->id,
441 "ref" => $invoice->ref,
443 "status" => $invoice->getLibStatut(1),
444 "customer" => $invoice->thirdparty->name,
445 "total_ht" =>
price($invoice->total_ht),
446 "total_ttc" =>
price($invoice->total_ttc),
448 "url" => DOL_URL_ROOT .
"/compta/facture/card.php?id=" . $invoice->id
462 if ($user ===
null) {
463 return [
"error" =>
"User not authenticated."];
468 if (!$user->hasRight(
'facture',
'creer')) {
469 return [
"error" =>
"Permission denied: validating an invoice requires the right facture/creer."];
472 if (is_array($invoice)) {
476 if ($invoice->statut != 0) {
477 return [
"error" =>
"Invoice is already validated."];
480 if ($invoice->validate($user) < 0) {
481 $error = $invoice->error;
482 if (!empty($invoice->errors)) {
483 $error .=
' ' . implode(
', ', $invoice->errors);
485 if (empty(trim($error))) {
486 $error =
'Unknown error (validate returned < 0 with no message)';
488 return [
"error" =>
"Validation failed: " . $error];
491 $invoice->fetch($invoice->id);
494 "new_ref" => $invoice->ref,
495 "status" =>
"Validated (Unpaid)",
496 "url" => DOL_URL_ROOT .
"/compta/facture/card.php?id=" . $invoice->id
510 if ($user ===
null) {
511 return [
"error" =>
"User not authenticated."];
515 if (!$user->hasRight(
'facture',
'paiement')) {
516 return [
"error" =>
"Permission denied: recording a payment requires the right facture/paiement."];
519 if (is_array($invoice)) {
524 if ($invoice->statut == 0) {
525 return [
"error" =>
"Cannot pay a Draft invoice. Please validate it first."];
528 $bank = $this->
findBankAccount(isset($args[
'bank_account']) ? $args[
'bank_account'] :
'');
530 return [
"error" =>
"No active Bank account found to receive payment."];
533 $remaining = $invoice->total_ttc - $invoice->getSommePaiement();
534 if ($remaining <= 0) {
535 return [
"error" =>
"Invoice is already fully paid."];
538 $amount = isset($args[
'amount']) ? (float) $args[
'amount'] : $remaining;
539 if ($amount > $remaining) {
540 $amount = $remaining;
543 $code = isset($args[
'payment_mode']) ? $args[
'payment_mode'] :
'VIR';
548 $payment->datepaye =
dol_now();
549 $payment->amounts = [$invoice->id => $amount];
550 $payment->paiementid = $modeId;
551 $payment->paiementcode = $code;
553 $paymentId = $payment->create($user, 1);
554 if ($paymentId < 0) {
555 $this->db->rollback();
556 return [
"error" =>
"Payment creation failed: " . implode(
', ', $payment->errors)];
558 $payment->fetch($paymentId);
559 if ($payment->addPaymentToBank($user,
'payment',
'(Payment via AI)', $bank->rowid,
'',
'') < 0) {
560 $this->db->rollback();
561 return [
"error" =>
"Failed to add payment to bank ledger."];
568 "paid_amount" =>
price($amount),
569 "remaining_due" =>
price($remaining - $amount),
570 "status" => ($remaining - $amount <= 0) ?
"Fully Paid" :
"Partially Paid",
571 "payment_url" => DOL_URL_ROOT .
"/compta/paiement/card.php?id=" . $paymentId
583 if (!$this->
user->hasRight(
'fournisseur',
'facture',
'lire')) {
584 return [
"error" =>
"Permission denied: supplier invoice read right is missing."];
587 $limit = min(25, max(1, (
int) ($args[
'limit'] ?? 10)));
588 $query = trim((
string) ($args[
'query'] ??
''));
589 $status = (string) ($args[
'status'] ??
'');
591 $sql =
"SELECT f.rowid FROM ".MAIN_DB_PREFIX.
"facture_fourn as f";
592 $sql .=
" LEFT JOIN ".MAIN_DB_PREFIX.
"societe as s ON s.rowid = f.fk_soc";
593 $sql .=
" WHERE f.entity IN (".getEntity(
'facture_fourn').
")";
595 if (!empty($args[
'ids']) && is_array($args[
'ids'])) {
596 foreach ($args[
'ids'] as $selId) {
597 if ((
int) $selId > 0 && count($onlyIds) < 100) {
598 $onlyIds[] = (int) $selId;
601 if (!empty($onlyIds)) {
602 $sql .=
" AND f.rowid IN (".$this->db->sanitize(implode(
',', $onlyIds)).
")";
606 $q = $this->db->escape($this->db->escapeforlike($query));
607 $sql .=
" AND (f.ref LIKE '%".$this->db->escape($q).
"%' OR f.ref_supplier LIKE '%".$this->db->escape($q).
"%' OR s.nom LIKE '%".$this->db->escape($q).
"%')";
609 if ($status ===
'draft') {
610 $sql .=
" AND f.fk_statut = 0";
611 } elseif ($status ===
'unpaid') {
612 $sql .=
" AND f.fk_statut = 1 AND f.paye = 0";
613 } elseif ($status ===
'paid') {
614 $sql .=
" AND f.paye = 1";
616 $sql .=
" ORDER BY f.datef DESC";
617 $sql .=
" LIMIT ".((int) $limit);
619 $resql = $this->db->query($sql);
621 dol_syslog(
"AiToolsInvoices::searchSupplierInvoices sql error", LOG_ERR);
622 return [
"error" =>
"Search failed."];
625 require_once DOL_DOCUMENT_ROOT.
'/fourn/class/fournisseur.facture.class.php';
627 while ($obj = $this->db->fetch_object($resql)) {
629 if ($inv->fetch((
int) $obj->rowid) > 0) {
630 $inv->fetch_thirdparty();
634 "ref_supplier" => $inv->ref_supplier,
635 "supplier" => is_object($inv->thirdparty) ? $inv->thirdparty->name :
'',
637 "status" => $inv->getLibStatut(1),
638 "total_ttc" =>
price($inv->total_ttc),
639 "_ht" => (float) $inv->total_ht,
640 "_ttc" => (
float) $inv->total_ttc,
641 "url" => DOL_URL_ROOT.
"/fourn/facture/card.php?facid=".$inv->id
646 if (!empty($out) && !empty($onlyIds)) {
649 foreach ($out as $row) {
650 $sumHt += $row[
'_ht'];
651 $sumTtc += $row[
'_ttc'];
653 foreach ($out as $k => $row) {
654 unset($out[$k][
'_ht'], $out[$k][
'_ttc']);
657 "count" => count($out),
658 "total_ht" =>
price($sumHt),
659 "total_ttc" =>
price($sumTtc),
663 foreach ($out as $k => $row) {
664 unset($out[$k][
'_ht'], $out[$k][
'_ttc']);
667 return empty($out) ? [
"message" =>
"No supplier invoices found."] : $out;
678 if (!$this->
user->hasRight(
'fournisseur',
'facture',
'lire')) {
679 return [
"error" =>
"Permission denied: supplier invoice read right is missing."];
682 require_once DOL_DOCUMENT_ROOT.
'/fourn/class/fournisseur.facture.class.php';
685 if (!empty($args[
'id'])) {
686 $res = $inv->fetch((
int) $args[
'id']);
687 } elseif (!empty($args[
'ref'])) {
688 $res = $inv->fetch(0, (
string) $args[
'ref']);
690 if ($res <= 0 || empty($inv->id)) {
691 return [
"error" =>
"Supplier invoice not found."];
693 if (!in_array((
int) $inv->entity, explode(
',',
getEntity(
'facture_fourn')))) {
694 return [
"error" =>
"Supplier invoice not found."];
697 $inv->fetch_thirdparty();
701 foreach ($inv->lines as $l) {
702 $lvars = get_object_vars($l);
704 "product" => !empty($l->product_ref) ? $l->product_ref :
'',
706 "qty" => (float) $l->qty,
707 "price" =>
price($lvars[
'pu_ht'] ?? $lvars[
'subprice'] ?? 0),
708 "total_line" =>
price($l->total_ht),
709 "vat" => $l->tva_tx.
"%"
716 "ref_supplier" => $inv->ref_supplier,
718 "status" => $inv->getLibStatut(1),
719 "supplier" => is_object($inv->thirdparty) ? $inv->thirdparty->name :
'',
720 "total_ht" =>
price($inv->total_ht),
721 "total_ttc" =>
price($inv->total_ttc),
723 "url" => DOL_URL_ROOT.
"/fourn/facture/card.php?facid=".$inv->id
737 $customer =
new Societe($this->db);
738 $identifier = trim($identifier);
740 if (preg_match(
'/^(?:socid|id)[:\s]+(\d+)$/i', $identifier, $m)) {
742 } elseif (preg_match(
'/^(?:code|ref)[:\s]+(.+)$/i', $identifier, $m)) {
746 if (is_numeric($identifier)) {
747 if ($customer->fetch((
int) $identifier) > 0) {
753 $sql =
"SELECT rowid FROM " . MAIN_DB_PREFIX .
"societe
754 WHERE (nom = '" . $this->db->escape($identifier) .
"'
755 OR code_client = '" . $this->db->escape($identifier) .
"')
756 AND entity IN (" .
getEntity(
'societe') .
")";
758 $resql = $this->db->query($sql);
760 if ($resql && $this->db->num_rows($resql) > 0) {
761 $obj = $this->db->fetch_object($resql);
762 $customer->fetch($obj->rowid);
763 $this->db->free($resql);
767 $sql =
"SELECT rowid, nom FROM " . MAIN_DB_PREFIX .
"societe
768 WHERE (nom LIKE '%" . $this->db->escape($identifier) .
"%'
769 OR code_client LIKE '%" . $this->db->escape($identifier) .
"%')
770 AND entity IN (" .
getEntity(
'societe') .
")
773 $resql = $this->db->query($sql);
776 $num = $this->db->num_rows($resql);
779 $obj = $this->db->fetch_object($resql);
780 $customer->fetch($obj->rowid);
781 $this->db->free($resql);
783 } elseif ($num > 1) {
785 while ($obj = $this->db->fetch_object($resql)) {
786 $matches[] = $obj->nom;
788 $this->db->free($resql);
789 return [
"error" =>
"Multiple customers found.",
"matches" => $matches];
793 return [
"error" =>
"Customer not found."];
804 $invoice =
new Facture($this->db);
805 $identifier = trim($identifier);
807 if (preg_match(
'/^\(?prov[-_]?(\d+)\)?$/i', $identifier, $matches)) {
808 if ($invoice->fetch((
int) $matches[1]) > 0) {
812 if (is_numeric($identifier)) {
813 if ($invoice->fetch((
int) $identifier) > 0) {
817 if ($invoice->fetch(0, $identifier) > 0) {
821 return [
"error" =>
"Invoice not found."];
834 $identifier = trim($identifier);
838 $sql =
"SELECT rowid, label FROM " . MAIN_DB_PREFIX .
"bank_account
839 WHERE entity IN (" .
getEntity(
'bank_account') .
") AND clos = 0";
841 if (is_numeric($identifier)) {
842 $sql .=
" AND rowid = " . ((int) $identifier);
843 } elseif (!empty($identifier)) {
844 $sql .=
" AND (ref = '" . $this->db->escape($identifier) .
"'
845 OR label LIKE '%" . $this->db->escape($identifier) .
"%')";
848 $resql = $this->db->query($sql);
850 if ($resql && $this->db->num_rows($resql) > 0) {
851 $obj = $this->db->fetch_object($resql);
852 $this->db->free($resql);
857 $sql =
"SELECT rowid, label FROM " . MAIN_DB_PREFIX .
"bank_account
858 WHERE entity IN (" .
getEntity(
'bank_account') .
") AND clos = 0
861 $resql = $this->db->query($sql);
863 if ($resql && $this->db->num_rows($resql) > 0) {
864 $obj = $this->db->fetch_object($resql);
865 $this->db->free($resql);
$id
Support class for third parties, contacts, members, users or resources.
Class to manage Dolibarr database access.
Class to manage suppliers invoices.
Class to manage invoices.
Class to manage payments of customer invoices.
Class to manage third parties objects (customers, suppliers, prospects...)
dol_html_entity_decode($a, $b, $c='UTF-8', $keepsomeentities=0)
Replace html_entity_decode functions to manage errors.
dol_now($mode='gmt')
Return date for now.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
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($dolibarr_main_document_root, $realpathconf=null)
Load conf file (file must exists)
$conf db user
Active Directory does not allow anonymous connections.