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.",
108 "name" =>
"get_invoice",
109 "description" =>
"Get details of a specific invoice by ID or Reference.",
113 "ref" => [
"type" =>
"string",
"description" =>
"Invoice Ref (e.g. FA2401-001)"],
114 "id" => [
"type" =>
"integer",
"description" =>
"Invoice ID"]
117 [
"required" => [
"ref"]],
118 [
"required" => [
"id"]]
123 "name" =>
"validate_invoice",
124 "description" =>
"Validate a draft invoice.",
128 "invoice" => [
"type" =>
"string",
"description" =>
"Invoice ID or Ref."]
130 "required" => [
"invoice"]
134 "name" =>
"pay_invoice",
135 "description" =>
"Register a payment for an invoice.",
139 "invoice" => [
"type" =>
"string",
"description" =>
"Invoice ID or Reference."],
140 "amount" => [
"type" =>
"number",
"description" =>
"Amount to pay. Defaults to full remaining."],
141 "payment_mode" => [
"type" =>
"string",
"description" =>
"Code (VIR, CB, LIQ)."],
142 "bank_account" => [
"type" =>
"string",
"description" =>
"Bank Account Name/Ref."]
144 "required" => [
"invoice"]
168 public function execute(
string $name, array $args)
171 case 'search_invoice':
172 case 'search_invoices':
178 case 'validate_invoice':
185 return [
"error" =>
"Tool function '$name' not found."];
198 $limit = isset($args[
'limit']) ? (int) $args[
'limit'] : 10;
199 $status = isset($args[
'status']) ? $args[
'status'] :
'unpaid';
206 dol_syslog(
"Search DB Error: Too many record requested", LOG_ERR);
207 return [
"error" =>
"DB Error"];
210 $sql =
"SELECT f.rowid, f.ref, f.total_ttc, f.fk_statut, f.paye, f.datef, s.nom
211 FROM " . MAIN_DB_PREFIX .
"facture as f
212 LEFT JOIN " . MAIN_DB_PREFIX .
"societe as s ON f.fk_soc = s.rowid
213 WHERE f.entity IN (" .
getEntity(
'facture') .
")";
216 if ($status ===
'draft') {
218 $sql .=
" AND f.fk_statut = 0";
219 } elseif ($status ===
'paid') {
221 $sql .=
" AND f.fk_statut = 2";
222 } elseif ($status ===
'all') {
224 $sql .=
" AND f.fk_statut IN (1, 2)";
228 $sql .=
" AND f.fk_statut = 1 AND f.paye = 0";
232 if (!empty($args[
'customer'])) {
234 if (!is_array($cust)) {
235 $sql .=
" AND f.fk_soc = " . ((int) $cust->id);
237 $sql .=
" AND s.nom LIKE '%" . $this->db->escape($args[
'customer']) .
"%'";
241 $sql .=
" ORDER BY f.datef DESC LIMIT " . ((int) $limit);
243 $resql = $this->db->query($sql);
247 while ($r = $this->db->fetch_object($resql)) {
249 if ($status !==
'draft' && $r->fk_statut == 0) {
253 $ref = ($r->fk_statut == 0 ?
"(PROV" . $r->rowid .
")" : $r->ref);
256 $statusLabel =
"Unknown";
257 if ($r->fk_statut == 0) {
258 $statusLabel =
"Draft";
259 } elseif ($r->fk_statut == 1) {
260 $statusLabel =
"Unpaid";
261 } elseif ($r->fk_statut == 2) {
262 $statusLabel =
"Paid";
263 } elseif ($r->fk_statut == 3) {
264 $statusLabel =
"Abandoned";
270 "customer" => $r->nom,
271 "amount" =>
price($r->total_ttc),
272 "status" => $statusLabel,
273 "url" => DOL_URL_ROOT .
"/compta/facture/card.php?id=" . $r->rowid
276 $this->db->free($resql);
280 return [
"info" =>
"No " . $status .
" invoices found matching your criteria."];
295 $id = isset($args[
'ref']) ? $args[
'ref'] : (isset($args[
'id']) ? $args[
'id'] :
null);
298 if (is_array($invoice)) {
302 $invoice->fetch_thirdparty();
303 $invoice->fetch_lines();
306 foreach ($invoice->lines as $l) {
307 $prodRef = !empty($l->product_ref) ? $l->product_ref : (!empty($l->product_label) ? $l->product_label :
'');
310 "product" => $prodRef,
312 "qty" => (float) $l->qty,
313 "price" =>
price($l->subprice),
314 "total_line" =>
price($l->total_ht),
315 "vat" => $l->tva_tx .
"%"
320 "id" => $invoice->id,
321 "ref" => $invoice->ref,
323 "status" => $invoice->getLibStatut(1),
324 "customer" => $invoice->thirdparty->name,
325 "total_ht" =>
price($invoice->total_ht),
326 "total_ttc" =>
price($invoice->total_ttc),
328 "url" => DOL_URL_ROOT .
"/compta/facture/card.php?id=" . $invoice->id
342 if ($user ===
null) {
343 return [
"error" =>
"User not authenticated."];
346 if (is_array($invoice)) {
350 if ($invoice->statut != 0) {
351 return [
"error" =>
"Invoice is already validated."];
354 if ($invoice->validate($user) < 0) {
355 $error = $invoice->error;
356 if (!empty($invoice->errors)) {
357 $error .=
' ' . implode(
', ', $invoice->errors);
359 if (empty(trim($error))) {
360 $error =
'Unknown error (validate returned < 0 with no message)';
362 return [
"error" =>
"Validation failed: " . $error];
365 $invoice->fetch($invoice->id);
368 "new_ref" => $invoice->ref,
369 "status" =>
"Validated (Unpaid)",
370 "url" => DOL_URL_ROOT .
"/compta/facture/card.php?id=" . $invoice->id
384 if ($user ===
null) {
385 return [
"error" =>
"User not authenticated."];
388 if (is_array($invoice)) {
393 if ($invoice->statut == 0) {
394 return [
"error" =>
"Cannot pay a Draft invoice. Please validate it first."];
397 $bank = $this->
findBankAccount(isset($args[
'bank_account']) ? $args[
'bank_account'] :
'');
399 return [
"error" =>
"No active Bank account found to receive payment."];
402 $remaining = $invoice->total_ttc - $invoice->getSommePaiement();
403 if ($remaining <= 0) {
404 return [
"error" =>
"Invoice is already fully paid."];
407 $amount = isset($args[
'amount']) ? (float) $args[
'amount'] : $remaining;
408 if ($amount > $remaining) {
409 $amount = $remaining;
412 $code = isset($args[
'payment_mode']) ? $args[
'payment_mode'] :
'VIR';
417 $payment->datepaye =
dol_now();
418 $payment->amounts = [$invoice->id => $amount];
419 $payment->paiementid = $modeId;
420 $payment->paiementcode = $code;
422 $paymentId = $payment->create($user, 1);
423 if ($paymentId < 0) {
424 $this->db->rollback();
425 return [
"error" =>
"Payment creation failed: " . implode(
', ', $payment->errors)];
427 $payment->fetch($paymentId);
428 if ($payment->addPaymentToBank($user,
'payment',
'(Payment via AI)', $bank->rowid,
'',
'') < 0) {
429 $this->db->rollback();
430 return [
"error" =>
"Failed to add payment to bank ledger."];
437 "paid_amount" =>
price($amount),
438 "remaining_due" =>
price($remaining - $amount),
439 "status" => ($remaining - $amount <= 0) ?
"Fully Paid" :
"Partially Paid",
440 "payment_url" => DOL_URL_ROOT .
"/compta/paiement/card.php?id=" . $paymentId
454 $customer =
new Societe($this->db);
455 $identifier = trim($identifier);
457 if (preg_match(
'/^(?:socid|id)[:\s]+(\d+)$/i', $identifier, $m)) {
459 } elseif (preg_match(
'/^(?:code|ref)[:\s]+(.+)$/i', $identifier, $m)) {
463 if (is_numeric($identifier)) {
464 if ($customer->fetch((
int) $identifier) > 0) {
470 $sql =
"SELECT rowid FROM " . MAIN_DB_PREFIX .
"societe
471 WHERE (nom = '" . $this->db->escape($identifier) .
"'
472 OR code_client = '" . $this->db->escape($identifier) .
"')
473 AND entity IN (" .
getEntity(
'societe') .
")";
475 $resql = $this->db->query($sql);
477 if ($resql && $this->db->num_rows($resql) > 0) {
478 $obj = $this->db->fetch_object($resql);
479 $customer->fetch($obj->rowid);
480 $this->db->free($resql);
484 $sql =
"SELECT rowid, nom FROM " . MAIN_DB_PREFIX .
"societe
485 WHERE (nom LIKE '%" . $this->db->escape($identifier) .
"%'
486 OR code_client LIKE '%" . $this->db->escape($identifier) .
"%')
487 AND entity IN (" .
getEntity(
'societe') .
")
490 $resql = $this->db->query($sql);
493 $num = $this->db->num_rows($resql);
496 $obj = $this->db->fetch_object($resql);
497 $customer->fetch($obj->rowid);
498 $this->db->free($resql);
500 } elseif ($num > 1) {
502 while ($obj = $this->db->fetch_object($resql)) {
503 $matches[] = $obj->nom;
505 $this->db->free($resql);
506 return [
"error" =>
"Multiple customers found.",
"matches" => $matches];
510 return [
"error" =>
"Customer not found."];
521 $invoice =
new Facture($this->db);
522 $identifier = trim($identifier);
524 if (preg_match(
'/^\(?prov[-_]?(\d+)\)?$/i', $identifier, $matches)) {
525 if ($invoice->fetch((
int) $matches[1]) > 0) {
529 if (is_numeric($identifier)) {
530 if ($invoice->fetch((
int) $identifier) > 0) {
534 if ($invoice->fetch(0, $identifier) > 0) {
538 return [
"error" =>
"Invoice not found."];
551 $identifier = trim($identifier);
555 $sql =
"SELECT rowid, label FROM " . MAIN_DB_PREFIX .
"bank_account
556 WHERE entity IN (" .
getEntity(
'bank_account') .
") AND clos = 0";
558 if (is_numeric($identifier)) {
559 $sql .=
" AND rowid = " . ((int) $identifier);
560 } elseif (!empty($identifier)) {
561 $sql .=
" AND (ref = '" . $this->db->escape($identifier) .
"'
562 OR label LIKE '%" . $this->db->escape($identifier) .
"%')";
565 $resql = $this->db->query($sql);
567 if ($resql && $this->db->num_rows($resql) > 0) {
568 $obj = $this->db->fetch_object($resql);
569 $this->db->free($resql);
574 $sql =
"SELECT rowid, label FROM " . MAIN_DB_PREFIX .
"bank_account
575 WHERE entity IN (" .
getEntity(
'bank_account') .
") AND clos = 0
578 $resql = $this->db->query($sql);
580 if ($resql && $this->db->num_rows($resql) > 0) {
581 $obj = $this->db->fetch_object($resql);
582 $this->db->free($resql);
$id
Support class for third parties, contacts, members, users or resources.
Class to manage Dolibarr database access.
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.