1<?php 2/* Copyright (C) 2001-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org> 3 * Copyright (C) 2004-2015 Laurent Destailleur <eldy@users.sourceforge.net> 4 * Copyright (C) 2005-2014 Regis Houssin <regis.houssin@inodbox.com> 5 * Copyright (C) 2014-2016 Charlie BENKE <charlie@patas-monkey.com> 6 * Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr> 7 * Copyright (C) 2019 Pierre Ardoin <mapiolca@me.com> 8 * Copyright (C) 2019 Frédéric France <frederic.france@netlogic.fr> 9 * Copyright (C) 2019 Nicolas ZABOURI <info@inovea-conseil.com> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 3 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program. If not, see <https://www.gnu.org/licenses/>. 23 */ 24 25/** 26 * \file htdocs/product/index.php 27 * \ingroup product 28 * \brief Homepage products and services 29 */ 30 31require '../main.inc.php'; 32require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; 33require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; 34require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php'; 35 36$type = GETPOST("type", 'int'); 37if ($type == '' && !$user->rights->produit->lire) $type = '1'; // Force global page on service page only 38if ($type == '' && !$user->rights->service->lire) $type = '0'; // Force global page on product page only 39 40// Security check 41if ($type == '0') $result = restrictedArea($user, 'produit'); 42elseif ($type == '1') $result = restrictedArea($user, 'service'); 43else $result = restrictedArea($user, 'produit|service|expedition'); 44 45// Load translation files required by the page 46$langs->loadLangs(array('products', 'stocks')); 47 48// Initialize technical object to manage hooks. Note that conf->hooks_modules contains array of hooks 49$hookmanager->initHooks(array('productindex')); 50 51$product_static = new Product($db); 52 53 54/* 55 * View 56 */ 57 58$transAreaType = $langs->trans("ProductsAndServicesArea"); 59 60$helpurl = ''; 61if (!isset($_GET["type"])) 62{ 63 $transAreaType = $langs->trans("ProductsAndServicesArea"); 64 $helpurl = 'EN:Module_Products|FR:Module_Produits|ES:Módulo_Productos'; 65} 66if ((isset($_GET["type"]) && $_GET["type"] == 0) || empty($conf->service->enabled)) 67{ 68 $transAreaType = $langs->trans("ProductsArea"); 69 $helpurl = 'EN:Module_Products|FR:Module_Produits|ES:Módulo_Productos'; 70} 71if ((isset($_GET["type"]) && $_GET["type"] == 1) || empty($conf->product->enabled)) 72{ 73 $transAreaType = $langs->trans("ServicesArea"); 74 $helpurl = 'EN:Module_Services_En|FR:Module_Services|ES:Módulo_Servicios'; 75} 76 77llxHeader("", $langs->trans("ProductsAndServices"), $helpurl); 78 79$linkback = ""; 80print load_fiche_titre($transAreaType, $linkback, 'product'); 81 82 83print '<div class="fichecenter"><div class="fichethirdleft">'; 84 85 86if (!empty($conf->global->MAIN_SEARCH_FORM_ON_HOME_AREAS)) // This is useless due to the global search combo 87{ 88 // Search contract 89 if ((!empty($conf->product->enabled) || !empty($conf->service->enabled)) && ($user->rights->produit->lire || $user->rights->service->lire)) 90 { 91 $listofsearchfields['search_product'] = array('text'=>'ProductOrService'); 92 } 93 94 if (count($listofsearchfields)) 95 { 96 print '<form method="post" action="'.DOL_URL_ROOT.'/core/search.php">'; 97 print '<input type="hidden" name="token" value="'.newToken().'">'; 98 print '<div class="div-table-responsive-no-min">'; 99 print '<table class="noborder nohover centpercent">'; 100 $i = 0; 101 foreach ($listofsearchfields as $key => $value) 102 { 103 if ($i == 0) print '<tr class="liste_titre"><td colspan="3">'.$langs->trans("Search").'</td></tr>'; 104 print '<tr class="oddeven">'; 105 print '<td class="nowrap"><label for="'.$key.'">'.$langs->trans($value["text"]).'</label></td><td><input type="text" class="flat inputsearch" name="'.$key.'" id="'.$key.'" size="18"></td>'; 106 if ($i == 0) print '<td rowspan="'.count($listofsearchfields).'"><input type="submit" value="'.$langs->trans("Search").'" class="button"></td>'; 107 print '</tr>'; 108 $i++; 109 } 110 print '</table>'; 111 print '</div>'; 112 print '</form>'; 113 print '<br>'; 114 } 115} 116 117/* 118 * Number of products and/or services 119 */ 120if ((!empty($conf->product->enabled) || !empty($conf->service->enabled)) && ($user->rights->produit->lire || $user->rights->service->lire)) 121{ 122 $prodser = array(); 123 $prodser[0][0] = $prodser[0][1] = $prodser[0][2] = $prodser[0][3] = 0; 124 $prodser[1][0] = $prodser[1][1] = $prodser[1][2] = $prodser[1][3] = 0; 125 126 $sql = "SELECT COUNT(p.rowid) as total, p.fk_product_type, p.tosell, p.tobuy"; 127 $sql .= " FROM ".MAIN_DB_PREFIX."product as p"; 128 $sql .= ' WHERE p.entity IN ('.getEntity($product_static->element, 1).')'; 129 // Add where from hooks 130 $parameters = array(); 131 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook 132 $sql .= $hookmanager->resPrint; 133 $sql .= " GROUP BY p.fk_product_type, p.tosell, p.tobuy"; 134 $result = $db->query($sql); 135 while ($objp = $db->fetch_object($result)) 136 { 137 $status = 3; // On sale + On purchase 138 if (!$objp->tosell && !$objp->tobuy) $status = 0; // Not on sale, not on purchase 139 if ($objp->tosell && !$objp->tobuy) $status = 1; // On sale only 140 if (!$objp->tosell && $objp->tobuy) $status = 2; // On purchase only 141 $prodser[$objp->fk_product_type][$status] = $objp->total; 142 if ($objp->tosell) $prodser[$objp->fk_product_type]['sell'] += $objp->total; 143 if ($objp->tobuy) $prodser[$objp->fk_product_type]['buy'] += $objp->total; 144 if (!$objp->tosell && !$objp->tobuy) $prodser[$objp->fk_product_type]['none'] += $objp->total; 145 } 146 147 if ($conf->use_javascript_ajax) 148 { 149 print '<div class="div-table-responsive-no-min">'; 150 print '<table class="noborder centpercent">'; 151 print '<tr class="liste_titre"><th>'.$langs->trans("Statistics").'</th></tr>'; 152 print '<tr><td class="center nopaddingleftimp nopaddingrightimp">'; 153 154 $SommeA = $prodser[0]['sell']; 155 $SommeB = $prodser[0]['buy']; 156 $SommeC = $prodser[0]['none']; 157 $SommeD = $prodser[1]['sell']; 158 $SommeE = $prodser[1]['buy']; 159 $SommeF = $prodser[1]['none']; 160 $total = 0; 161 $dataval = array(); 162 $datalabels = array(); 163 $i = 0; 164 165 $total = $SommeA + $SommeB + $SommeC + $SommeD + $SommeE + $SommeF; 166 $dataseries = array(); 167 if (!empty($conf->product->enabled)) 168 { 169 $dataseries[] = array($langs->transnoentitiesnoconv("ProductsOnSale"), round($SommeA)); 170 $dataseries[] = array($langs->transnoentitiesnoconv("ProductsOnPurchase"), round($SommeB)); 171 $dataseries[] = array($langs->transnoentitiesnoconv("ProductsNotOnSell"), round($SommeC)); 172 } 173 if (!empty($conf->service->enabled)) 174 { 175 $dataseries[] = array($langs->transnoentitiesnoconv("ServicesOnSale"), round($SommeD)); 176 $dataseries[] = array($langs->transnoentitiesnoconv("ServicesOnPurchase"), round($SommeE)); 177 $dataseries[] = array($langs->transnoentitiesnoconv("ServicesNotOnSell"), round($SommeF)); 178 } 179 include_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; 180 $dolgraph = new DolGraph(); 181 $dolgraph->SetData($dataseries); 182 $dolgraph->setShowLegend(2); 183 $dolgraph->setShowPercent(0); 184 $dolgraph->SetType(array('pie')); 185 $dolgraph->setHeight('200'); 186 $dolgraph->draw('idgraphstatus'); 187 print $dolgraph->show($total ? 0 : 1); 188 189 print '</td></tr>'; 190 print '</table>'; 191 print '</div>'; 192 } 193} 194 195 196if (!empty($conf->categorie->enabled) && !empty($conf->global->CATEGORY_GRAPHSTATS_ON_PRODUCTS)) 197{ 198 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; 199 print '<br>'; 200 print '<div class="div-table-responsive-no-min">'; 201 print '<table class="noborder centpercent">'; 202 print '<tr class="liste_titre"><th colspan="2">'.$langs->trans("Categories").'</th></tr>'; 203 print '<tr class="oddeven"><td class="center" colspan="2">'; 204 $sql = "SELECT c.label, count(*) as nb"; 205 $sql .= " FROM ".MAIN_DB_PREFIX."categorie_product as cs"; 206 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."categorie as c ON cs.fk_categorie = c.rowid"; 207 $sql .= " WHERE c.type = 0"; 208 $sql .= " AND c.entity IN (".getEntity('category').")"; 209 $sql .= " GROUP BY c.label"; 210 $total = 0; 211 $result = $db->query($sql); 212 if ($result) 213 { 214 $num = $db->num_rows($result); 215 $i = 0; 216 if (!empty($conf->use_javascript_ajax)) 217 { 218 $dataseries = array(); 219 $rest = 0; 220 $nbmax = 10; 221 while ($i < $num) 222 { 223 $obj = $db->fetch_object($result); 224 if ($i < $nbmax) 225 { 226 $dataseries[] = array($obj->label, round($obj->nb)); 227 } else { 228 $rest += $obj->nb; 229 } 230 $total += $obj->nb; 231 $i++; 232 } 233 if ($i > $nbmax) 234 { 235 $dataseries[] = array($langs->trans("Other"), round($rest)); 236 } 237 238 include_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; 239 $dolgraph = new DolGraph(); 240 $dolgraph->SetData($dataseries); 241 $dolgraph->setShowLegend(2); 242 $dolgraph->setShowPercent(1); 243 $dolgraph->SetType(array('pie')); 244 $dolgraph->setHeight('200'); 245 $dolgraph->draw('idstatscategproduct'); 246 print $dolgraph->show($total ? 0 : 1); 247 } else { 248 while ($i < $num) 249 { 250 $obj = $db->fetch_object($result); 251 252 print '<tr><td>'.$obj->label.'</td><td>'.$obj->nb.'</td></tr>'; 253 $total += $obj->nb; 254 $i++; 255 } 256 } 257 } 258 print '</td></tr>'; 259 print '<tr class="liste_total"><td>'.$langs->trans("Total").'</td><td class="right">'; 260 print $total; 261 print '</td></tr>'; 262 print '</table>'; 263 print '</div>'; 264} 265print '</div><div class="fichetwothirdright"><div class="ficheaddleft">'; 266 267 268/* 269 * Latest modified products 270 */ 271if ((!empty($conf->product->enabled) || !empty($conf->service->enabled)) && ($user->rights->produit->lire || $user->rights->service->lire)) 272{ 273 $max = 15; 274 $sql = "SELECT p.rowid, p.label, p.price, p.ref, p.fk_product_type, p.tosell, p.tobuy, p.tobatch, p.fk_price_expression,"; 275 $sql .= " p.entity,"; 276 $sql .= " p.tms as datem"; 277 $sql .= " FROM ".MAIN_DB_PREFIX."product as p"; 278 $sql .= " WHERE p.entity IN (".getEntity($product_static->element, 1).")"; 279 if ($type != '') $sql .= " AND p.fk_product_type = ".$type; 280 // Add where from hooks 281 $parameters = array(); 282 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook 283 $sql .= $hookmanager->resPrint; 284 $sql .= $db->order("p.tms", "DESC"); 285 $sql .= $db->plimit($max, 0); 286 287 //print $sql; 288 $result = $db->query($sql); 289 if ($result) 290 { 291 $num = $db->num_rows($result); 292 293 $i = 0; 294 295 if ($num > 0) 296 { 297 $transRecordedType = $langs->trans("LastModifiedProductsAndServices", $max); 298 if (isset($_GET["type"]) && $_GET["type"] == 0) $transRecordedType = $langs->trans("LastRecordedProducts", $max); 299 if (isset($_GET["type"]) && $_GET["type"] == 1) $transRecordedType = $langs->trans("LastRecordedServices", $max); 300 301 print '<div class="div-table-responsive-no-min">'; 302 print '<table class="noborder centpercent">'; 303 304 $colnb = 2; 305 if (empty($conf->global->PRODUIT_MULTIPRICES)) $colnb++; 306 307 print '<tr class="liste_titre"><th colspan="'.$colnb.'">'.$transRecordedType.'</th>'; 308 print '<th class="right" colspan="3"><a href="'.DOL_URL_ROOT.'/product/list.php?sortfield=p.tms&sortorder=DESC">'.$langs->trans("FullList").'</td>'; 309 print '</tr>'; 310 311 while ($i < $num) 312 { 313 $objp = $db->fetch_object($result); 314 315 $product_static->id = $objp->rowid; 316 $product_static->ref = $objp->ref; 317 $product_static->label = $objp->label; 318 $product_static->type = $objp->fk_product_type; 319 $product_static->entity = $objp->entity; 320 $product_static->status = $objp->tosell; 321 $product_static->status_buy = $objp->tobuy; 322 $product_static->status_batch = $objp->tobatch; 323 324 //Multilangs 325 if (!empty($conf->global->MAIN_MULTILANGS)) 326 { 327 $sql = "SELECT label"; 328 $sql .= " FROM ".MAIN_DB_PREFIX."product_lang"; 329 $sql .= " WHERE fk_product=".$objp->rowid; 330 $sql .= " AND lang='".$db->escape($langs->getDefaultLang())."'"; 331 332 $resultd = $db->query($sql); 333 if ($resultd) 334 { 335 $objtp = $db->fetch_object($resultd); 336 if ($objtp && $objtp->label != '') $objp->label = $objtp->label; 337 } 338 } 339 340 341 print '<tr class="oddeven">'; 342 print '<td class="nowrap">'; 343 print $product_static->getNomUrl(1, '', 16); 344 print "</td>\n"; 345 print '<td>'.dol_trunc($objp->label, 32).'</td>'; 346 print "<td>"; 347 print dol_print_date($db->jdate($objp->datem), 'day'); 348 print "</td>"; 349 // Sell price 350 if (empty($conf->global->PRODUIT_MULTIPRICES)) 351 { 352 if (!empty($conf->dynamicprices->enabled) && !empty($objp->fk_price_expression)) 353 { 354 $product = new Product($db); 355 $product->fetch($objp->rowid); 356 $priceparser = new PriceParser($db); 357 $price_result = $priceparser->parseProduct($product); 358 if ($price_result >= 0) { 359 $objp->price = $price_result; 360 } 361 } 362 print '<td class="nowrap right">'; 363 if (isset($objp->price_base_type) && $objp->price_base_type == 'TTC') print price($objp->price_ttc).' '.$langs->trans("TTC"); 364 else print price($objp->price).' '.$langs->trans("HT"); 365 print '</td>'; 366 } 367 print '<td class="right nowrap width25"><span class="statusrefsell">'; 368 print $product_static->LibStatut($objp->tosell, 3, 0); 369 print "</span></td>"; 370 print '<td class="right nowrap width25"><span class="statusrefbuy">'; 371 print $product_static->LibStatut($objp->tobuy, 3, 1); 372 print "</span></td>"; 373 print "</tr>\n"; 374 $i++; 375 } 376 377 $db->free($result); 378 379 print "</table>"; 380 print '</div>'; 381 print '<br>'; 382 } 383 } else { 384 dol_print_error($db); 385 } 386} 387 388 389// TODO Move this into a page that should be available into menu "accountancy - report - turnover - per quarter" 390// Also method used for counting must provide the 2 possible methods like done by all other reports into menu "accountancy - report - turnover": 391// "commitment engagment" method and "cash accounting" method 392if (!empty($conf->global->MAIN_SHOW_PRODUCT_ACTIVITY_TRIM)) 393{ 394 if (!empty($conf->product->enabled)) activitytrim(0); 395 if (!empty($conf->service->enabled)) activitytrim(1); 396} 397 398 399print '</div></div></div>'; 400 401$parameters = array('type' => $type, 'user' => $user); 402$reshook = $hookmanager->executeHooks('dashboardProductsServices', $parameters, $object); // Note that $action and $object may have been modified by hook 403 404// End of page 405llxFooter(); 406$db->close(); 407 408 409/** 410 * Print html activity for product type 411 * 412 * @param int $product_type Type of product 413 * @return void 414 */ 415function activitytrim($product_type) 416{ 417 global $conf, $langs, $db; 418 419 // We display the last 3 years 420 $yearofbegindate = date('Y', dol_time_plus_duree(time(), -3, "y")); 421 422 // breakdown by quarter 423 $sql = "SELECT DATE_FORMAT(p.datep,'%Y') as annee, DATE_FORMAT(p.datep,'%m') as mois, SUM(fd.total_ht) as Mnttot"; 424 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f, ".MAIN_DB_PREFIX."facturedet as fd"; 425 $sql .= " , ".MAIN_DB_PREFIX."paiement as p,".MAIN_DB_PREFIX."paiement_facture as pf"; 426 $sql .= " WHERE f.entity IN (".getEntity('invoice').")"; 427 $sql .= " AND f.rowid = fd.fk_facture"; 428 $sql .= " AND pf.fk_facture = f.rowid"; 429 $sql .= " AND pf.fk_paiement= p.rowid"; 430 $sql .= " AND fd.product_type=".$product_type; 431 $sql .= " AND p.datep >= '".$db->idate(dol_get_first_day($yearofbegindate), 1)."'"; 432 $sql .= " GROUP BY annee, mois "; 433 $sql .= " ORDER BY annee, mois "; 434 435 $result = $db->query($sql); 436 if ($result) 437 { 438 $tmpyear = 0; 439 $trim1 = 0; 440 $trim2 = 0; 441 $trim3 = 0; 442 $trim4 = 0; 443 $lgn = 0; 444 $num = $db->num_rows($result); 445 446 if ($num > 0) 447 { 448 print '<div class="div-table-responsive-no-min">'; 449 print '<table class="noborder" width="75%">'; 450 451 if ($product_type == 0) 452 print '<tr class="liste_titre"><td class=left>'.$langs->trans("ProductSellByQuarterHT").'</td>'; 453 else print '<tr class="liste_titre"><td class=left>'.$langs->trans("ServiceSellByQuarterHT").'</td>'; 454 print '<td class=right>'.$langs->trans("Quarter1").'</td>'; 455 print '<td class=right>'.$langs->trans("Quarter2").'</td>'; 456 print '<td class=right>'.$langs->trans("Quarter3").'</td>'; 457 print '<td class=right>'.$langs->trans("Quarter4").'</td>'; 458 print '<td class=right>'.$langs->trans("Total").'</td>'; 459 print '</tr>'; 460 } 461 $i = 0; 462 463 while ($i < $num) 464 { 465 $objp = $db->fetch_object($result); 466 if ($tmpyear != $objp->annee) 467 { 468 if ($trim1 + $trim2 + $trim3 + $trim4 > 0) 469 { 470 print '<tr class="oddeven"><td class=left>'.$tmpyear.'</td>'; 471 print '<td class="nowrap right">'.price($trim1).'</td>'; 472 print '<td class="nowrap right">'.price($trim2).'</td>'; 473 print '<td class="nowrap right">'.price($trim3).'</td>'; 474 print '<td class="nowrap right">'.price($trim4).'</td>'; 475 print '<td class="nowrap right">'.price($trim1 + $trim2 + $trim3 + $trim4).'</td>'; 476 print '</tr>'; 477 $lgn++; 478 } 479 // We go to the following year 480 $tmpyear = $objp->annee; 481 $trim1 = 0; 482 $trim2 = 0; 483 $trim3 = 0; 484 $trim4 = 0; 485 } 486 487 if ($objp->mois == "01" || $objp->mois == "02" || $objp->mois == "03") 488 $trim1 += $objp->Mnttot; 489 490 if ($objp->mois == "04" || $objp->mois == "05" || $objp->mois == "06") 491 $trim2 += $objp->Mnttot; 492 493 if ($objp->mois == "07" || $objp->mois == "08" || $objp->mois == "09") 494 $trim3 += $objp->Mnttot; 495 496 if ($objp->mois == "10" || $objp->mois == "11" || $objp->mois == "12") 497 $trim4 += $objp->Mnttot; 498 499 $i++; 500 } 501 if ($trim1 + $trim2 + $trim3 + $trim4 > 0) 502 { 503 print '<tr class="oddeven"><td class=left>'.$tmpyear.'</td>'; 504 print '<td class="nowrap right">'.price($trim1).'</td>'; 505 print '<td class="nowrap right">'.price($trim2).'</td>'; 506 print '<td class="nowrap right">'.price($trim3).'</td>'; 507 print '<td class="nowrap right">'.price($trim4).'</td>'; 508 print '<td class="nowrap right">'.price($trim1 + $trim2 + $trim3 + $trim4).'</td>'; 509 print '</tr>'; 510 } 511 if ($num > 0) 512 print '</table></div>'; 513 } 514} 515