dolibarr 25.0.0-alpha
reports.class.php
1<?php
2/* Copyright (C) 2026 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2026 Nick Fragoulis
4 * Copyright (C) 2026 Jose Martinez <jose.martinez@pichinov.com>
5 * Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
31require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
32require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
33
39class ToolReports extends McpTool
40{
50 public function __construct(DoliDB $db, $user = null, $conf = null)
51 {
52 $this->db = $db;
53 $this->user = $user;
54 if ($conf !== null) {
55 $this->conf = $conf;
56 }
57 }
58
64 public function getDefinitions(): array
65 {
66 return [
67 [
68 "name" => "get_thirdparty_transactions",
69 "description" => "Generate a list of raw transactions (Invoices, Orders) for a specific thirdparty.",
70 "inputSchema" => [
71 "type" => "object",
72 "properties" => [
73 "thirdparty_id" => ["type" => "integer", "description" => "The unique ID of the thirdparty."],
74 "thirdparty_name" => ["type" => "string", "description" => "The name of the thirdparty."],
75 "date_start" => ["type" => "string", "description" => "Start date (YYYY-MM-DD)."],
76 "date_end" => ["type" => "string", "description" => "End date (YYYY-MM-DD)."],
77 "transaction_type" => [
78 "type" => "string",
79 "enum" => ["all", "invoices", "orders", "proposals"],
80 "description" => "Filter by type.",
81 "default" => "all"
82 ]
83 ],
84 "oneOf" => [
85 ["required" => ["thirdparty_id"]],
86 ["required" => ["thirdparty_name"]]
87 ]
88 ]
89 ],
90 [
91 "name" => "get_sales_report",
92 "description" => "Generate a sales/revenue report. If a Thirdparty is provided, it returns a detailed breakdown for that customer. Otherwise, it returns a global summary.",
93 "inputSchema" => [
94 "type" => "object",
95 "properties" => [
96 "thirdparty_id" => [
97 "type" => "integer",
98 "description" => "Optional: The ID of the customer."
99 ],
100 "date_start" => ["type" => "string", "description" => "Start date (YYYY-MM-DD)."],
101 "date_end" => ["type" => "string", "description" => "End date (YYYY-MM-DD)."],
102 "group_by" => [
103 "type" => "string",
104 "enum" => ["thirdparty", "product", "month"],
105 "description" => "Only used if no Thirdparty is specified. Groups global results.",
106 "default" => "thirdparty"
107 ]
108 ],
109 "required" => ["date_start", "date_end"]
110 ]
111 ],
112 [
113 "name" => "get_purchase_report",
114 "description" => "Generate a purchase/expense report. If a Supplier is provided, it returns a detailed breakdown. Otherwise, it returns a global summary.",
115 "inputSchema" => [
116 "type" => "object",
117 "properties" => [
118 "thirdparty_id" => [
119 "type" => "integer",
120 "description" => "Optional: The ID of the supplier."
121 ],
122 "date_start" => ["type" => "string", "description" => "Start date (YYYY-MM-DD)."],
123 "date_end" => ["type" => "string", "description" => "End date (YYYY-MM-DD)."],
124 "group_by" => [
125 "type" => "string",
126 "enum" => ["supplier", "product", "month"],
127 "description" => "Only used if no Supplier is specified. Groups global results.",
128 "default" => "supplier"
129 ]
130 ],
131 "required" => ["date_start", "date_end"]
132 ]
133 ],
134 [
135 "name" => "get_inventory_report",
136 "description" => "Generate an inventory report showing current stock levels and valuation.",
137 "inputSchema" => [
138 "type" => "object",
139 "properties" => [
140 "category_id" => ["type" => "integer", "description" => "Filter by category ID."],
141 "warehouse_id" => ["type" => "integer", "description" => "Filter by warehouse ID."],
142 "include_zero_stock" => ["type" => "boolean", "default" => false]
143 ]
144 ]
145 ],
146 [
147 "name" => "get_financial_report",
148 "description" => "Generate a summary financial report (Income vs Expense) for a period.",
149 "inputSchema" => [
150 "type" => "object",
151 "properties" => [
152 "date_start" => ["type" => "string", "description" => "Start date (YYYY-MM-DD)."],
153 "date_end" => ["type" => "string", "description" => "End date (YYYY-MM-DD)."]
154 ],
155 "required" => ["date_start", "date_end"]
156 ]
157 ],
158 ];
159 }
160
167 public function getCategories(): array
168 {
169 return ['reporting', 'commercial', 'billing', 'stock'];
170 }
171
179 public function execute(string $name, array $args)
180 {
181 switch ($name) {
182 case 'get_thirdparty_transactions':
183 return $this->getThirdpartyTransactions($args);
184 case 'get_sales_report':
185 return $this->getSalesReport($args);
186 case 'get_purchase_report':
187 return $this->getPurchaseReport($args);
188 case 'get_inventory_report':
189 return $this->getInventoryReport($args);
190 case 'get_financial_report':
191 return $this->getFinancialReport($args);
192 default:
193 return ["error" => "Tool function '$name' not found."];
194 }
195 }
196
208 private function resolveThirdparty($args)
209 {
210
211 if (!empty($args['thirdparty_id'])) {
212 return (int) $args['thirdparty_id'];
213 }
214
215 if (!empty($args['thirdparty_name'])) {
216 $sqlName = $this->db->escape($args['thirdparty_name']);
217
218 $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "societe
219 WHERE nom LIKE '%" . $sqlName . "%'
220 AND entity IN (" . getEntity('societe') . ")
221 LIMIT 1";
222
223 $resql = $this->db->query($sql);
224 if ($resql && $obj = $this->db->fetch_object($resql)) {
225 return $obj->rowid;
226 }
227 }
228
229 return null;
230 }
231
238 private function getSalesReport(array $args): array
239 {
240 global $langs;
241
242 $langs->loadLangs(array("main", "bills", "companies", "products"));
243
244 $limit = isset($args['limit']) ? (int) $args['limit'] : 50;
245 $dateStart = dol_stringtotime($args['date_start']);
246 $dateEnd = dol_stringtotime($args['date_end']);
247 $socid = $this->resolveThirdparty($args);
248 $groupBy = isset($args['group_by']) ? (string) $args['group_by'] : 'thirdparty';
249
250 $list = [];
251 $totalSum = 0.0;
252 // Status Filter: Valid (1) and Paid (2). Exclude Draft (0) and Abandoned (3).
253 $dateRange = " AND f.datef >= '" . $this->db->idate($dateStart)
254 . "' AND f.datef <= '" . $this->db->idate($dateEnd)
255 . "' AND f.fk_statut IN (1, 2)";
256
257 // CASE 1 -- Detailed list for a specific thirdparty.
258 if ($socid) {
259 $sql = "SELECT f.rowid, f.ref, f.total_ttc, f.fk_statut, f.paye, f.datef, s.nom FROM "
260 . MAIN_DB_PREFIX . "facture as f LEFT JOIN "
261 . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid WHERE f.entity IN ("
262 . getEntity('facture') . ")"
263 . $dateRange
264 . " AND f.fk_soc = " . (int) $socid
265 . " ORDER BY f.datef DESC LIMIT " . ((int) $limit);
266
267 $resql = $this->db->query($sql);
268 if ($resql) {
269 while ($r = $this->db->fetch_object($resql)) {
270 $totalSum += (float) $r->total_ttc;
271
272 $statusLabel = $langs->transnoentitiesnoconv("Unknown");
273 if ($r->fk_statut == 1 && $r->paye == 0) {
274 $statusLabel = $langs->transnoentitiesnoconv("BillStatusNotPaid");
275 } elseif ($r->fk_statut == 1 && $r->paye == 1) {
276 $statusLabel = $langs->transnoentitiesnoconv("BillStatusStarted");
277 } elseif ($r->fk_statut == 2) {
278 $statusLabel = $langs->transnoentitiesnoconv("BillStatusPaid");
279 }
280
281 $url = DOL_URL_ROOT . "/compta/facture/card.php?id=" . $r->rowid;
282 $refHtml = '<a href="' . $url . '">' . $r->ref . '</a>';
283
284 $list[] = [
285 $langs->transnoentitiesnoconv("Ref") => $refHtml,
286 $langs->transnoentitiesnoconv("Date") => dol_print_date($this->db->jdate($r->datef), 'day'),
287 $langs->transnoentitiesnoconv("Customer") => $r->nom,
288 $langs->transnoentitiesnoconv("Amount") => price($r->total_ttc),
289 $langs->transnoentitiesnoconv("Status") => $statusLabel
290 ];
291 }
292 $this->db->free($resql);
293 }
294 } else {
295 // CASE 2 -- Global grouped report.
296 // Mirrors the pattern already used by getPurchaseReport(); previous implementation
297 // of getSalesReport() ignored $groupBy entirely and always returned a flat list.
298 $sanitizedSqlGroup = '';
299 $colName = '';
300 $sqlJoin = " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid";
301
302 if ($groupBy === 'month') {
303 $sanitizedSqlGroup = "DATE_FORMAT(f.datef, '%Y-%m')";
304 $colName = $langs->transnoentitiesnoconv("Month");
305 } elseif ($groupBy === 'product') {
306 // Aggregate on product line items. Lines without product_id fall back to their description.
307 $sanitizedSqlGroup = "COALESCE(p.ref, fd.description, '?')";
308 $colName = $langs->transnoentitiesnoconv("Product");
309 $sqlJoin .= " INNER JOIN " . MAIN_DB_PREFIX . "facturedet as fd ON fd.fk_facture = f.rowid LEFT JOIN "
310 . MAIN_DB_PREFIX . "product as p ON fd.fk_product = p.rowid";
311 } else {
312 // Default: group by customer
313 $sanitizedSqlGroup = "s.nom";
314 $colName = $langs->transnoentitiesnoconv("Customer");
315 }
316
317 // For product grouping we sum line totals (more accurate per-product);
318 // otherwise we sum the invoice total_ttc.
319 $amountExpr = ($groupBy === 'product') ? "SUM(fd.total_ttc)" : "SUM(f.total_ttc)";
320 $countExpr = ($groupBy === 'product') ? "COUNT(DISTINCT f.rowid)" : "COUNT(f.rowid)";
321
322 $sql = "SELECT " . $sanitizedSqlGroup . " as group_key, "
323 . $amountExpr . " as total_amount, "
324 . $countExpr . " as count_inv FROM "
325 . MAIN_DB_PREFIX . "facture as f"
326 . $sqlJoin
327 . " WHERE f.entity IN (" . getEntity('facture') . ")"
328 . $dateRange
329 . " GROUP BY group_key ORDER BY total_amount DESC LIMIT "
330 . ((int) max(1, $limit));
331
332 $resql = $this->db->query($sql);
333 if ($resql) {
334 while ($r = $this->db->fetch_object($resql)) {
335 $totalSum += (float) $r->total_amount;
336 $list[] = [
337 $colName => $r->group_key ? $r->group_key : $langs->transnoentitiesnoconv('Unknown'),
338 $langs->transnoentitiesnoconv("Number") => (int) $r->count_inv,
339 $langs->transnoentitiesnoconv("Amount") => price($r->total_amount)
340 ];
341 }
342 $this->db->free($resql);
343 }
344 }
345
346 if (empty($list)) {
347 return [[$langs->transnoentitiesnoconv("Info") => $langs->transnoentitiesnoconv("NoRecordFound")]];
348 }
349
350 // Append Total Row (shape depends on detailed-vs-grouped path)
351 if ($socid) {
352 $list[] = [
353 $langs->transnoentitiesnoconv("Ref") => $langs->transnoentitiesnoconv("Total"),
354 $langs->transnoentitiesnoconv("Date") => "",
355 $langs->transnoentitiesnoconv("Customer") => "",
356 $langs->transnoentitiesnoconv("Amount") => price($totalSum),
357 $langs->transnoentitiesnoconv("Status") => ""
358 ];
359 } else {
360 $list[] = [
361 $langs->transnoentitiesnoconv("Total") => $langs->transnoentitiesnoconv("Total"),
362 $langs->transnoentitiesnoconv("Amount") => price($totalSum)
363 ];
364 }
365
366 return $list;
367 }
368
375 private function getThirdpartyTransactions(array $args): array
376 {
377 global $langs;
378
379 $langs->loadLangs(array("main", "bills", "orders", "propal"));
380
381 $dateStart = dol_stringtotime($args['date_start']);
382 $dateEnd = dol_stringtotime($args['date_end']);
383 $type = isset($args['transaction_type']) ? (string) $args['transaction_type'] : 'all';
384
385 $socid = $this->resolveThirdparty($args);
386 if (!$socid) {
387 $langs->load("errors");
388 return [[$langs->transnoentitiesnoconv("Error") => $langs->transnoentitiesnoconv("ErrorThirdPartyNotFound")]];
389 }
390
391 $sqlQueries = [];
392
393 // Invoices
394 if ($type == 'all' || $type == 'invoices') {
395 $sqlQueries[] = "SELECT 'Invoice' as source_type, rowid, ref, total_ttc as amount, datef as date_entry, fk_statut
396 FROM " . MAIN_DB_PREFIX . "facture
397 WHERE fk_soc = " . (int) $socid . " AND entity IN (" . getEntity('facture') . ")
398 AND fk_statut IN (1, 2)";
399 }
400
401 // Orders
402 if ($type == 'all' || $type == 'orders') {
403 $sqlQueries[] = "SELECT 'Order' as source_type, rowid, ref, total_ttc as amount, date_commande as date_entry, fk_statut
404 FROM " . MAIN_DB_PREFIX . "commande
405 WHERE fk_soc = " . (int) $socid . " AND entity IN (" . getEntity('commande') . ")
406 AND fk_statut > 0";
407 }
408
409 // Proposals
410 if ($type == 'all' || $type == 'proposals') {
411 $sqlQueries[] = "SELECT 'Proposal' as source_type, rowid, ref, total_ttc as amount, datep as date_entry, fk_statut
412 FROM " . MAIN_DB_PREFIX . "propal
413 WHERE fk_soc = " . (int) $socid . " AND entity IN (" . getEntity('propal') . ")
414 AND fk_statut > 0";
415 }
416
417 if (empty($sqlQueries)) {
418 return [[$langs->transnoentitiesnoconv("Error") => "Invalid transaction type"]];
419 }
420
421 $sql = "SELECT * FROM (";
422 $sql .= implode(" UNION ", $sqlQueries);
423 $sql .= ") as combined_transactions ";
424 $whereParts = [];
425 if ($dateStart > 0) {
426 $whereParts[] = "date_entry >= '" . $this->db->idate($dateStart) . "'";
427 }
428 if ($dateEnd > 0) {
429 $whereParts[] = "date_entry <= '" . $this->db->idate($dateEnd) . "'";
430 }
431
432 if (!empty($whereParts)) {
433 $sql .= " WHERE " . implode(" AND ", $whereParts);
434 }
435
436 $sql .= " ORDER BY date_entry DESC";
437
438 $resql = $this->db->query($sql);
439 $list = [];
440 $totalAmt = 0.0;
441
442 if ($resql) {
443 while ($r = $this->db->fetch_object($resql)) {
444 $totalAmt += (float) $r->amount;
445
446 $statusTxt = "";
447 $urlPath = "";
448
449 if ($r->source_type === 'Invoice') {
450 $urlPath = "/compta/facture/card.php?id=" . $r->rowid;
451 if ($r->fk_statut == 2) {
452 $statusTxt = $langs->transnoentitiesnoconv("BillStatusPaid");
453 } elseif ($r->fk_statut == 1) {
454 $statusTxt = $langs->transnoentitiesnoconv("BillStatusNotPaid");
455 }
456 } elseif ($r->source_type === 'Order') {
457 $urlPath = "/commande/card.php?id=" . $r->rowid;
458 if ($r->fk_statut == 1) {
459 $statusTxt = $langs->transnoentitiesnoconv("StatusOrderValidated");
460 } elseif ($r->fk_statut == 2) {
461 $statusTxt = $langs->transnoentitiesnoconv("StatusOrderOnProcess");
462 } elseif ($r->fk_statut == 3) {
463 $statusTxt = $langs->transnoentitiesnoconv("StatusOrderDelivered");
464 }
465 } elseif ($r->source_type === 'Proposal') {
466 $urlPath = "/comm/propal/card.php?id=" . $r->rowid;
467 if ($r->fk_statut == 1) {
468 $statusTxt = $langs->transnoentitiesnoconv("PropalStatusValidated");
469 } elseif ($r->fk_statut == 2) {
470 $statusTxt = $langs->transnoentitiesnoconv("PropalStatusSigned");
471 } elseif ($r->fk_statut == 3) {
472 $statusTxt = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
473 } elseif ($r->fk_statut == 4) {
474 $statusTxt = $langs->transnoentitiesnoconv("PropalStatusBilled");
475 }
476 }
477
478 $fullUrl = $urlPath ? DOL_URL_ROOT . $urlPath : "";
479 $refHtml = $fullUrl ? '<a href="' . $fullUrl . '">' . $r->ref . '</a>' : $r->ref;
480
481 $list[] = [
482 $langs->transnoentitiesnoconv("Type") => $langs->transnoentitiesnoconv($r->source_type),
483 $langs->transnoentitiesnoconv("Ref") => $refHtml,
484 $langs->transnoentitiesnoconv("Date") => dol_print_date($this->db->jdate($r->date_entry), 'day'),
485 $langs->transnoentitiesnoconv("Amount") => price($r->amount),
486 $langs->transnoentitiesnoconv("Status") => $statusTxt
487 ];
488 }
489 $this->db->free($resql);
490 }
491
492 if (empty($list)) {
493 return [[$langs->transnoentitiesnoconv("Info") => $langs->transnoentitiesnoconv("NoRecordFound")]];
494 }
495
496 // Summary
497 $list[] = [
498 $langs->transnoentitiesnoconv("Type") => $langs->transnoentitiesnoconv("Total"),
499 $langs->transnoentitiesnoconv("Ref") => "",
500 $langs->transnoentitiesnoconv("Date") => "",
501 $langs->transnoentitiesnoconv("Amount") => price($totalAmt),
502 $langs->transnoentitiesnoconv("Status") => ""
503 ];
504
505 return $list;
506 }
507
514 private function getPurchaseReport(array $args): array
515 {
516 global $langs;
517
518 $langs->loadLangs(array("main", "bills", "companies"));
519
520 $socid = $this->resolveThirdparty($args);
521 $groupBy = isset($args['group_by']) ? (string) $args['group_by'] : 'supplier';
522 $dateStart = dol_stringtotime($args['date_start']);
523 $dateEnd = dol_stringtotime($args['date_end']);
524 $list = [];
525 $totalSum = 0.0;
526
527 // Detailed report for a specific Supplier
528 if ($socid) {
529 $sql = "SELECT f.rowid, f.ref, f.total_ttc, f.datef, s.nom
530 FROM " . MAIN_DB_PREFIX . "facture_fourn as f
531 LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid
532 WHERE f.entity IN (" . getEntity('facture_fourn') . ")
533 AND f.fk_soc = " . (int) $socid . "
534 AND f.datef >= '" . $this->db->idate($dateStart) . "'
535 AND f.datef <= '" . $this->db->idate($dateEnd) . "'
536 AND f.fk_statut > 0
537 ORDER BY f.datef DESC";
538
539 $resql = $this->db->query($sql);
540 if ($resql) {
541 while ($r = $this->db->fetch_object($resql)) {
542 $totalSum += (float) $r->total_ttc;
543
544 $url = DOL_URL_ROOT . "/fourn/facture/card.php?id=" . $r->rowid;
545 $refHtml = '<a href="' . $url . '">' . $r->ref . '</a>';
546
547 $list[] = [
548 $langs->transnoentitiesnoconv("Ref") => $refHtml,
549 $langs->transnoentitiesnoconv("Date") => dol_print_date($this->db->jdate($r->datef), 'day'),
550 $langs->transnoentitiesnoconv("Supplier") => $r->nom,
551 $langs->transnoentitiesnoconv("Amount") => price($r->total_ttc)
552 ];
553 }
554 $this->db->free($resql);
555 }
556 } else { // Global Grouped Report
557 $sanitizedSqlGroup = "";
558 $colName = "";
559
560 if ($groupBy === 'month') {
561 $sanitizedSqlGroup = "DATE_FORMAT(f.datef, '%Y-%m')";
562 $colName = $langs->transnoentitiesnoconv("Month");
563 } else {
564 $sanitizedSqlGroup = "s.nom";
565 $colName = $langs->transnoentitiesnoconv("Supplier");
566 }
567
568 $sql = "SELECT " . $sanitizedSqlGroup . " as group_key, SUM(f.total_ttc) as total_amount, COUNT(f.rowid) as count_inv
569 FROM " . MAIN_DB_PREFIX . "facture_fourn as f
570 LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid
571 WHERE f.entity IN (" . getEntity('facture_fourn') . ")
572 AND f.datef >= '" . $this->db->idate($dateStart) . "'
573 AND f.datef <= '" . $this->db->idate($dateEnd) . "'
574 AND f.fk_statut > 0
575 GROUP BY group_key
576 ORDER BY total_amount DESC";
577
578 $resql = $this->db->query($sql);
579 if ($resql) {
580 while ($r = $this->db->fetch_object($resql)) {
581 $totalSum += (float) $r->total_amount;
582 $list[] = [
583 $colName => $r->group_key ? $r->group_key : $langs->transnoentitiesnoconv('Unknown'),
584 $langs->transnoentitiesnoconv("Number") => $r->count_inv,
585 $langs->transnoentitiesnoconv("Amount") => price($r->total_amount)
586 ];
587 }
588 $this->db->free($resql);
589 }
590 }
591
592 if (empty($list)) {
593 return [[$langs->transnoentitiesnoconv("Info") => $langs->transnoentitiesnoconv("NoRecordFound")]];
594 }
595
596 // Summary Row
597 $summary = [
598 $langs->transnoentitiesnoconv("Amount") => price($totalSum)
599 ];
600
601 if ($socid) {
602 $summary[$langs->transnoentitiesnoconv("Ref")] = $langs->transnoentitiesnoconv("Total");
603 $summary[$langs->transnoentitiesnoconv("Date")] = "";
604 $summary[$langs->transnoentitiesnoconv("Supplier")] = "";
605 } else {
606 $summary[($groupBy === 'month' ? $langs->transnoentitiesnoconv("Month") : $langs->transnoentitiesnoconv("Supplier"))] = $langs->transnoentitiesnoconv("Total");
607 $summary[$langs->transnoentitiesnoconv("Number")] = "";
608 }
609 $list[] = $summary;
610
611 return $list;
612 }
613
620 private function getInventoryReport(array $args): array
621 {
622 global $langs;
623
624 $langs->loadLangs(array("products", "stocks"));
625
626 $catId = isset($args['category_id']) ? (int) $args['category_id'] : 0;
627 $warehouseId = isset($args['warehouse_id']) ? (int) $args['warehouse_id'] : 0;
628 $includeZero = isset($args['include_zero_stock']) ? (bool) $args['include_zero_stock'] : false;
629
630 $sql = "SELECT p.rowid, p.ref, p.label, p.pmp, ";
631
632 if ($warehouseId > 0) {
633 $sql .= " ps.reel as stock_level ";
634 $sql .= " FROM " . MAIN_DB_PREFIX . "product as p ";
635 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "product_stock as ps ON p.rowid = ps.fk_product ";
636 $sql .= " WHERE ps.fk_entrepot = " . (int) $warehouseId;
637 } else {
638 $sql .= " p.stock as stock_level ";
639 $sql .= " FROM " . MAIN_DB_PREFIX . "product as p ";
640 $sql .= " WHERE 1=1 ";
641 }
642
643 $sql .= " AND p.entity IN (" . getEntity('product') . ")";
644
645 if ($catId > 0) {
646 $sql .= " AND p.rowid IN (SELECT fk_product FROM " . MAIN_DB_PREFIX . "categorie_product WHERE fk_categorie = " . (int) $catId . ")";
647 }
648
649 if (!$includeZero) {
650 if ($warehouseId > 0) {
651 $sql .= " AND ps.reel > 0";
652 } else {
653 $sql .= " AND p.stock > 0";
654 }
655 }
656
657 $sql .= " ORDER BY p.ref ASC LIMIT 200";
658
659 $resql = $this->db->query($sql);
660 $list = [];
661 $totalValuation = 0.0;
662 $totalItems = 0;
663
664 if ($resql) {
665 while ($r = $this->db->fetch_object($resql)) {
666 $stockVal = $r->stock_level * $r->pmp;
667 $totalValuation += $stockVal;
668 $totalItems += (int) $r->stock_level;
669
670 $url = DOL_URL_ROOT . "/product/card.php?id=" . $r->rowid;
671 $refHtml = '<a href="' . $url . '">' . $r->ref . '</a>';
672
673 $list[] = [
674 $langs->transnoentitiesnoconv("Ref") => $refHtml,
675 $langs->transnoentitiesnoconv("Label") => $r->label,
676 $langs->transnoentitiesnoconv("Stock") => $r->stock_level,
677 $langs->transnoentitiesnoconv("PMPValue") => price($r->pmp),
678 $langs->transnoentitiesnoconv("TotalValue") => price($stockVal)
679 ];
680 }
681 $this->db->free($resql);
682 }
683
684 if (empty($list)) {
685 return [[$langs->transnoentitiesnoconv("Info") => $langs->transnoentitiesnoconv("NoRecordFound")]];
686 }
687
688 $list[] = [
689 $langs->transnoentitiesnoconv("Ref") => $langs->transnoentitiesnoconv("Total"),
690 $langs->transnoentitiesnoconv("Label") => "",
691 $langs->transnoentitiesnoconv("Stock") => $totalItems,
692 $langs->transnoentitiesnoconv("PMPValue") => "",
693 $langs->transnoentitiesnoconv("TotalValue") => price($totalValuation)
694 ];
695
696 return $list;
697 }
698
705 private function getFinancialReport(array $args): array
706 {
707 global $langs;
708
709 $langs->loadLangs(array("compta", "bills"));
710 $dateStart = dol_stringtotime($args['date_start']);
711 $dateEnd = dol_stringtotime($args['date_end']);
712
713 // Income (Customer Invoices - Validated/Paid, no Drafts)
714 $sqlIncome = "SELECT SUM(total_ttc) as total FROM " . MAIN_DB_PREFIX . "facture
715 WHERE entity IN (" . getEntity('facture') . ")
716 AND datef >= '" . $this->db->idate($dateStart) . "'
717 AND datef <= '" . $this->db->idate($dateEnd) . "'
718 AND fk_statut IN (1, 2)";
719
720 $resIncome = $this->db->query($sqlIncome);
721 $objIncome = $this->db->fetch_object($resIncome);
722 $income = $objIncome && $objIncome->total ? (float) $objIncome->total : 0.0;
723
724 // Expenses (Supplier Invoices - Validated, no Drafts)
725 $sqlExpense = "SELECT SUM(total_ttc) as total FROM " . MAIN_DB_PREFIX . "facture_fourn
726 WHERE entity IN (" . getEntity('facture_fourn') . ")
727 AND datef >= '" . $this->db->idate($dateStart) . "'
728 AND datef <= '" . $this->db->idate($dateEnd) . "'
729 AND fk_statut > 0";
730
731 $resExpense = $this->db->query($sqlExpense);
732 $objExpense = $this->db->fetch_object($resExpense);
733 $expense = $objExpense && $objExpense->total ? (float) $objExpense->total : 0.0;
734
735 $net = $income - $expense;
736
737 $list = [
738 [
739 $langs->transnoentitiesnoconv("Category") => $langs->transnoentitiesnoconv("Income"),
740 $langs->transnoentitiesnoconv("Description") => $langs->transnoentitiesnoconv("BillsCustomers"),
741 $langs->transnoentitiesnoconv("Amount") => price($income)
742 ],
743 [
744 $langs->transnoentitiesnoconv("Category") => $langs->transnoentitiesnoconv("Expenses"),
745 $langs->transnoentitiesnoconv("Description") => $langs->transnoentitiesnoconv("BillsSuppliers"),
746 $langs->transnoentitiesnoconv("Amount") => price($expense)
747 ],
748 [
749 $langs->transnoentitiesnoconv("Category") => $langs->transnoentitiesnoconv("Total"),
750 $langs->transnoentitiesnoconv("Description") => $langs->transnoentitiesnoconv("Profit"),
751 $langs->transnoentitiesnoconv("Amount") => price($net)
752 ]
753 ];
754
755 return $list;
756 }
757}
Class to manage Dolibarr database access.
Abstract base class for all MCP (Model Context Protocol) tools.
Class ToolReports.
getCategories()
Return categories this tool belongs to.
execute(string $name, array $args)
Executes the requested tool function based on its name.
getThirdpartyTransactions(array $args)
Generate a list of raw transactions (Invoices, Orders, Proposals).
getDefinitions()
Returns an array of tool definitions.
getPurchaseReport(array $args)
Generate a purchase/expense report.
getFinancialReport(array $args)
Generate a summary financial report (Income vs Expense).
getSalesReport(array $args)
Generate a sales/revenue report.
resolveThirdparty($args)
Resolves a Thirdparty ID from either an ID or a name.
__construct(DoliDB $db, $user=null, $conf=null)
Constructor.
getInventoryReport(array $args)
Generate an inventory report.
dol_stringtotime($string, $gm=1)
Convert a string date into a GM Timestamps date Warning: YYYY-MM-DDTHH:MM:SS+02:00 (RFC3339) is not s...
Definition date.lib.php:436
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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).
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)
Definition inc.php:429
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:134