1<?php 2/* Copyright (C) 2013 Cédric Salvador <csalvador@gpcsolutions.fr> 3 * Copyright (C) 2013-2020 Laurent Destaileur <ely@users.sourceforge.net> 4 * Copyright (C) 2014 Regis Houssin <regis.houssin@inodbox.com> 5 * Copyright (C) 2016 Juanjo Menent <jmenent@2byte.es> 6 * Copyright (C) 2016 ATM Consulting <support@atm-consulting.fr> 7 * Copyright (C) 2019 Frédéric France <frederic.france@netlogic.fr> 8 * 9 * This program is free software: you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation, either version 3 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program. If not, see <https://www.gnu.org/licenses/>. 21 */ 22 23/** 24 * \file htdocs/product/stock/stockatdate.php 25 * \ingroup stock 26 * \brief Page to list stocks at a given date 27 */ 28 29require '../../main.inc.php'; 30require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; 31require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; 32require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; 33require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php'; 34require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php'; 35require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php'; 36require_once './lib/replenishment.lib.php'; 37 38// Load translation files required by the page 39$langs->loadLangs(array('products', 'stocks', 'orders')); 40 41// Security check 42if ($user->socid) { 43 $socid = $user->socid; 44} 45$result = restrictedArea($user, 'produit|service'); 46 47// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context 48$hookmanager->initHooks(array('stockatdate')); 49 50//checks if a product has been ordered 51 52$action = GETPOST('action', 'aZ09'); 53$type = GETPOST('type', 'int'); 54$mode = GETPOST('mode', 'alpha'); 55 56$date = ''; 57$dateendofday = ''; 58if (GETPOSTISSET('dateday') && GETPOSTISSET('datemonth') && GETPOSTISSET('dateyear')) { 59 $date = dol_mktime(0, 0, 0, GETPOST('datemonth', 'int'), GETPOST('dateday', 'int'), GETPOST('dateyear', 'int')); 60 $dateendofday = dol_mktime(23, 59, 59, GETPOST('datemonth', 'int'), GETPOST('dateday', 'int'), GETPOST('dateyear', 'int')); 61} 62 63$search_ref = GETPOST('search_ref', 'alphanohtml'); 64$search_nom = GETPOST('search_nom', 'alphanohtml'); 65 66$now = dol_now(); 67 68$productid = GETPOST('productid', 'int'); 69$fk_warehouse = GETPOST('fk_warehouse', 'int'); 70 71$sortfield = GETPOST('sortfield', 'aZ09comma'); 72$sortorder = GETPOST('sortorder', 'aZ09comma'); 73$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); 74if (empty($page) || $page == -1) { $page = 0; } // If $page is not defined, or '' or -1 75$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit; 76$offset = $limit * $page; 77if (!$sortfield) { 78 $sortfield = 'p.ref'; 79} 80if (!$sortorder) { 81 $sortorder = 'ASC'; 82} 83 84$parameters = array(); 85$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks 86if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); 87 88$dateIsValid = true; 89if ($mode == 'future') { 90 if ($date && $date < $now) { 91 setEventMessages($langs->trans("ErrorDateMustBeInFuture"), null, 'errors'); 92 $dateIsValid = false; 93 } 94} else { 95 if ($date && $date > $now) { 96 setEventMessages($langs->trans("ErrorDateMustBeBeforeToday"), null, 'errors'); 97 $dateIsValid = false; 98 } 99} 100 101 102/* 103 * Actions 104 */ 105 106if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) // Both test are required to be compatible with all browsers 107{ 108 $date = ''; 109 $productid = 0; 110 $fk_warehouse = 0; 111 $search_ref = ''; 112 $search_nom = ''; 113} 114 115$warehouseStatus = array(); 116if ($conf->global->ENTREPOT_EXTRA_STATUS) { 117 //$warehouseStatus[] = Entrepot::STATUS_CLOSED; 118 $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL; 119 $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL; 120} 121 122// Get array with current stock per product, warehouse 123$stock_prod_warehouse = array(); 124$stock_prod = array(); 125if ($date && $dateIsValid) { // Avoid heavy sql if mandatory date is not defined 126 $sql = "SELECT ps.fk_product, ps.fk_entrepot as fk_warehouse,"; 127 $sql .= " SUM(ps.reel) AS stock"; 128 $sql .= " FROM ".MAIN_DB_PREFIX."product_stock as ps"; 129 $sql .= ", ".MAIN_DB_PREFIX."entrepot as w"; 130 $sql .= " WHERE w.entity IN (".getEntity('stock').")"; 131 $sql .= " AND w.rowid = ps.fk_entrepot"; 132 if (!empty($conf->global->ENTREPOT_EXTRA_STATUS) && count($warehouseStatus)) { 133 $sql .= " AND w.statut IN (".$db->sanitize($db->escape(implode(',', $warehouseStatus))).")"; 134 } 135 if ($productid > 0) { 136 $sql .= " AND ps.fk_product = ".$productid; 137 } 138 if ($fk_warehouse > 0) { 139 $sql .= " AND ps.fk_entrepot = ".$fk_warehouse; 140 } 141 $sql .= " GROUP BY fk_product, fk_entrepot"; 142 //print $sql; 143 144 $resql = $db->query($sql); 145 if ($resql) 146 { 147 $num = $db->num_rows($resql); 148 $i = 0; 149 150 while ($i < $num) { 151 $obj = $db->fetch_object($resql); 152 153 $tmp_fk_product = $obj->fk_product; 154 $tmp_fk_warehouse = $obj->fk_warehouse; 155 $stock = $obj->stock; 156 157 $stock_prod_warehouse[$tmp_fk_product][$tmp_fk_warehouse] = $stock; 158 $stock_prod[$tmp_fk_product] = (isset($stock_prod[$tmp_fk_product]) ? $stock_prod[$tmp_fk_product] : 0) + $stock; 159 160 $i++; 161 } 162 163 $db->free($resql); 164 } else { 165 dol_print_error($db); 166 } 167 //var_dump($stock_prod_warehouse); 168} elseif ($action == 'filter') { 169 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Date")), null, 'errors'); 170} 171 172// Get array with list of stock movements between date and now (for product/warehouse= 173$movements_prod_warehouse = array(); 174$movements_prod = array(); 175$movements_prod_warehouse_nb = array(); 176$movements_prod_nb = array(); 177if ($date && $dateIsValid) { 178 $sql = "SELECT sm.fk_product, sm.fk_entrepot, SUM(sm.value) AS stock, COUNT(sm.rowid) AS nbofmovement"; 179 $sql .= " FROM ".MAIN_DB_PREFIX."stock_mouvement as sm"; 180 $sql .= ", ".MAIN_DB_PREFIX."entrepot as w"; 181 $sql .= " WHERE w.entity IN (".getEntity('stock').")"; 182 $sql .= " AND w.rowid = sm.fk_entrepot"; 183 if (!empty($conf->global->ENTREPOT_EXTRA_STATUS) && count($warehouseStatus)) { 184 $sql .= " AND w.statut IN (".$db->sanitize($db->escape(implode(',', $warehouseStatus))).")"; 185 } 186 if ($mode == 'future') { 187 $sql .= " AND sm.datem <= '".$db->idate($dateendofday)."'"; 188 } else { 189 $sql .= " AND sm.datem >= '".$db->idate($date)."'"; 190 } 191 if ($productid > 0) { 192 $sql .= " AND sm.fk_product = ".$productid; 193 } 194 if ($fk_warehouse > 0) { 195 $sql .= " AND sm.fk_entrepot = ".$fk_warehouse; 196 } 197 $sql .= " GROUP BY sm.fk_product, sm.fk_entrepot"; 198 $resql = $db->query($sql); 199 200 if ($resql) 201 { 202 $num = $db->num_rows($resql); 203 $i = 0; 204 205 while ($i < $num) { 206 $obj = $db->fetch_object($resql); 207 $fk_product = $obj->fk_product; 208 $fk_entrepot = $obj->fk_entrepot; 209 $stock = $obj->stock; 210 $nbofmovement = $obj->nbofmovement; 211 212 // Pour llx_product_stock.reel 213 $movements_prod_warehouse[$fk_product][$fk_entrepot] = $stock; 214 $movements_prod_warehouse_nb[$fk_product][$fk_entrepot] = $nbofmovement; 215 216 // Pour llx_product.stock 217 $movements_prod[$fk_product] += $stock; 218 $movements_prod_nb[$fk_product] += $nbofmovement; 219 220 $i++; 221 } 222 223 $db->free($resql); 224 } else { 225 dol_print_error($db); 226 } 227} 228//var_dump($movements_prod_warehouse); 229//var_dump($movements_prod); 230 231 232/* 233 * View 234 */ 235 236$form = new Form($db); 237$formproduct = new FormProduct($db); 238$prod = new Product($db); 239 240$num = 0; 241 242$title = $langs->trans('StockAtDate'); 243 244$sql = 'SELECT p.rowid, p.ref, p.label, p.description, p.price,'; 245$sql .= ' p.price_ttc, p.price_base_type, p.fk_product_type, p.desiredstock, p.seuil_stock_alerte,'; 246$sql .= ' p.tms as datem, p.duration, p.tobuy, p.stock'; 247if ($fk_warehouse > 0) { 248 $sql .= ', SUM(ps.reel) as stock_reel'; 249} 250// Add fields from hooks 251$parameters = array(); 252$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters); // Note that $action and $object may have been modified by hook 253$sql .= $hookmanager->resPrint; 254 255$sql .= ' FROM '.MAIN_DB_PREFIX.'product as p'; 256if ($fk_warehouse > 0) { 257 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON p.rowid = ps.fk_product AND ps.fk_entrepot = '.$fk_warehouse; 258} 259// Add fields from hooks 260$parameters = array(); 261$reshook = $hookmanager->executeHooks('printFieldListJoin', $parameters); // Note that $action and $object may have been modified by hook 262$sql .= $hookmanager->resPrint; 263$sql .= ' WHERE p.entity IN ('.getEntity('product').')'; 264if ($productid > 0) { 265 $sql .= " AND p.rowid = ".$productid; 266} 267if (empty($conf->global->STOCK_SUPPORTS_SERVICES)) { 268 $sql .= " AND p.fk_product_type = 0"; 269} 270if (!empty($canvas)) $sql .= ' AND p.canvas = "'.$db->escape($canvas).'"'; 271if ($fk_warehouse > 0) { 272 $sql .= ' GROUP BY p.rowid, p.ref, p.label, p.description, p.price, p.price_ttc, p.price_base_type, p.fk_product_type, p.desiredstock, p.seuil_stock_alerte,'; 273 $sql .= ' p.tms, p.duration, p.tobuy, p.stock'; 274} 275// Add where from hooks 276$parameters = array(); 277$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook 278$sql .= $hookmanager->resPrint; 279 280if ($sortfield == 'stock_reel' && $fk_warehouse <= 0) { 281 $sortfield = 'stock'; 282} 283if ($sortfield == 'stock' && $fk_warehouse > 0) { 284 $sortfield = 'stock_reel'; 285} 286$sql .= $db->order($sortfield, $sortorder); 287 288if ($date && $dateIsValid) { // We avoid a heavy sql if mandatory parameter date not yet defined 289 $nbtotalofrecords = ''; 290 if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) 291 { 292 $result = $db->query($sql); 293 $nbtotalofrecords = $db->num_rows($result); 294 if (($page * $limit) > $nbtotalofrecords) // if total resultset is smaller then paging size (filtering), goto and load page 0 295 { 296 $page = 0; 297 $offset = 0; 298 } 299 } 300 301 $sql .= $db->plimit($limit + 1, $offset); 302 303 //print $sql; 304 $resql = $db->query($sql); 305 if (empty($resql)) 306 { 307 dol_print_error($db); 308 exit; 309 } 310 311 $num = $db->num_rows($resql); 312} 313 314$i = 0; 315//print $sql; 316 317$helpurl = 'EN:Module_Stocks_En|FR:Module_Stock|'; 318$helpurl .= 'ES:Módulo_Stocks'; 319 320llxHeader('', $title, $helpurl, ''); 321 322$head = array(); 323 324$head[0][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php'; 325$head[0][1] = $langs->trans("StockAtDateInPast"); 326$head[0][2] = 'stockatdatepast'; 327 328$head[1][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php?mode=future'; 329$head[1][1] = $langs->trans("StockAtDateInFuture"); 330$head[1][2] = 'stockatdatefuture'; 331 332 333print load_fiche_titre($langs->trans('StockAtDate'), '', 'stock'); 334 335print dol_get_fiche_head($head, ($mode == 'future' ? 'stockatdatefuture' : 'stockatdatepast'), '', -1, ''); 336 337$desc = $langs->trans("StockAtDatePastDesc"); 338if ($mode == 'future') $desc = $langs->trans("StockAtDateFutureDesc"); 339print '<span class="opacitymedium">'.$desc.'</span><br>'."\n"; 340print '<br>'."\n"; 341 342print '<form name="formFilterWarehouse" method="POST" action="'.$_SERVER["PHP_SELF"].'">'; 343print '<input type="hidden" name="token" value="'.newToken().'">'; 344print '<input type="hidden" name="action" value="filter">'; 345print '<input type="hidden" name="mode" value="'.$mode.'">'; 346 347print '<div class="inline-block valignmiddle" style="padding-right: 20px;">'; 348print '<span class="fieldrequired">'.$langs->trans('Date').'</span> '.$form->selectDate(($date ? $date : -1), 'date'); 349 350print ' <span class="clearbothonsmartphone marginleftonly paddingleftonly marginrightonly paddinrightonly"> </span> '; 351print img_picto('', 'product').' '; 352print $langs->trans('Product').'</span> '; 353$form->select_produits($productid, 'productid', '', 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'maxwidth300'); 354 355print ' <span class="clearbothonsmartphone marginleftonly paddingleftonly marginrightonly paddinrightonly"> </span> '; 356print img_picto('', 'stock').' '; 357print $langs->trans('Warehouse').'</span> '; 358print $formproduct->selectWarehouses((GETPOSTISSET('fk_warehouse') ? $fk_warehouse : 'ifone'), 'fk_warehouse', '', 1); 359print '</div>'; 360 361$parameters = array(); 362$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook 363if (empty($reshook)) print $hookmanager->resPrint; 364 365print '<div class="inline-block valignmiddle">'; 366print '<input class="button" type="submit" name="valid" value="'.$langs->trans('Refresh').'">'; 367print '</div>'; 368 369//print '</form>'; 370 371$param = ''; 372if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) $param .= '&contextpage='.urlencode($contextpage); 373if ($limit > 0 && $limit != $conf->liste_limit) $param .= '&limit='.urlencode($limit); 374$param .= '&mode='.$mode; 375if ($fk_warehouse > 0) $param .= '&fk_warehouse='.$fk_warehouse; 376if ($productid > 0) $param .= '&productid='.$productid; 377if (GETPOST('dateday', 'int') > 0) $param .= '&dateday='.GETPOST('dateday', 'int'); 378if (GETPOST('datemonth', 'int') > 0) $param .= '&datemonth='.GETPOST('datemonth', 'int'); 379if (GETPOST('dateyear', 'int') > 0) $param .= '&dateyear='.GETPOST('dateyear', 'int'); 380 381// TODO Move this into the title line ? 382print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'stock', 0, '', '', $limit, 0, 0, 1); 383 384print '<div class="div-table-responsive">'; // You can use div-table-responsive-no-min if you dont need reserved height for your table 385print '<table class="liste centpercent">'; 386 387$stocklabel = $langs->trans('StockAtDate'); 388if ($mode == 'future') $stocklabel = $langs->trans("VirtualStockAtDate"); 389 390//print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST" name="formulaire">'; 391print '<input type="hidden" name="token" value="'.newToken().'">'; 392print '<input type="hidden" name="sortfield" value="'.$sortfield.'">'; 393print '<input type="hidden" name="sortorder" value="'.$sortorder.'">'; 394print '<input type="hidden" name="type" value="'.$type.'">'; 395print '<input type="hidden" name="mode" value="'.$mode.'">'; 396 397// Fields title search 398print '<tr class="liste_titre_filter">'; 399print '<td class="liste_titre"><input class="flat" type="text" name="search_ref" size="8" value="'.dol_escape_htmltag($search_ref).'"></td>'; 400print '<td class="liste_titre"><input class="flat" type="text" name="search_nom" size="8" value="'.dol_escape_htmltag($search_nom).'"></td>'; 401print '<td class="liste_titre"></td>'; 402print '<td class="liste_titre"></td>'; 403print '<td class="liste_titre"></td>'; 404if ($mode == 'future') { 405 print '<td class="liste_titre"></td>'; 406} 407// Fields from hook 408$parameters = array('param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder); 409$reshook = $hookmanager->executeHooks('printFieldListOption', $parameters); // Note that $action and $object may have been modified by hook 410print $hookmanager->resPrint; 411 412print '<td class="liste_titre maxwidthsearch">'; 413$searchpicto = $form->showFilterAndCheckAddButtons(0); 414print $searchpicto; 415print '</td>'; 416print '</tr>'; 417 418$fieldtosortcurrentstock = 'stock'; 419if ($fk_warehouse > 0) { 420 $fieldtosortcurrentstock = 'stock_reel'; 421} 422 423// Lines of title 424print '<tr class="liste_titre">'; 425print_liste_field_titre('Ref', $_SERVER["PHP_SELF"], 'p.ref', $param, '', '', $sortfield, $sortorder); 426print_liste_field_titre('Label', $_SERVER["PHP_SELF"], 'p.label', $param, '', '', $sortfield, $sortorder); 427if ($mode == 'future') { 428 print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, $param, '', '', $sortfield, $sortorder, 'right '); 429 print_liste_field_titre('', $_SERVER["PHP_SELF"]); 430 print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ', 'VirtualStockAtDateDesc'); 431 print_liste_field_titre('VirtualStock', $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ', 'VirtualStockDesc'); 432} else { 433 print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right '); 434 print_liste_field_titre('', $_SERVER["PHP_SELF"]); 435 print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, $param, '', '', $sortfield, $sortorder, 'right '); 436} 437print_liste_field_titre('', $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right '); 438 439// Hook fields 440$parameters = array('param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder); 441$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook 442print $hookmanager->resPrint; 443 444print "</tr>\n"; 445 446$i = 0; 447while ($i < ($limit ? min($num, $limit) : $num)) 448{ 449 $objp = $db->fetch_object($resql); 450 451 if (!empty($conf->global->STOCK_SUPPORTS_SERVICES) || $objp->fk_product_type == 0) 452 { 453 $prod->fetch($objp->rowid); 454 455 // Multilangs 456 /*if (!empty($conf->global->MAIN_MULTILANGS)) 457 { 458 $sql = 'SELECT label,description'; 459 $sql .= ' FROM '.MAIN_DB_PREFIX.'product_lang'; 460 $sql .= ' WHERE fk_product = '.$objp->rowid; 461 $sql .= ' AND lang = "'.$langs->getDefaultLang().'"'; 462 $sql .= ' LIMIT 1'; 463 464 $resqlm = $db->query($sql); 465 if ($resqlm) 466 { 467 $objtp = $db->fetch_object($resqlm); 468 if (!empty($objtp->description)) $objp->description = $objtp->description; 469 if (!empty($objtp->label)) $objp->label = $objtp->label; 470 } 471 }*/ 472 473 $currentstock = ''; 474 if ($fk_warehouse > 0) 475 { 476 //if ($productid > 0) { 477 $currentstock = $stock_prod_warehouse[$objp->rowid][$fk_warehouse]; 478 //} else { 479 // $currentstock = $objp->stock_reel; 480 //} 481 } else { 482 //if ($productid > 0) { 483 $currentstock = $stock_prod[$objp->rowid]; 484 //} else { 485 // $currentstock = $objp->stock; 486 //} 487 } 488 489 if ($mode == 'future') { 490 $prod->load_stock('warehouseopen, warehouseinternal', 0); // This call also ->load_virtual_stock() 491 492 //$result = $prod->load_stats_reception(0, '4'); 493 //print $prod->stats_commande_fournisseur['qty'].'<br>'."\n"; 494 //print $prod->stats_reception['qty']; 495 496 $stock = '<span class="opacitymedium">'.$langs->trans("FeatureNotYetAvailable").'</span>'; 497 $virtualstock = $prod->stock_theorique; 498 } else { 499 if ($fk_warehouse > 0) { 500 $stock = $currentstock - $movements_prod_warehouse[$objp->rowid][$fk_warehouse]; 501 $nbofmovement = $movements_prod_warehouse_nb[$objp->rowid][$fk_warehouse]; 502 } else { 503 $stock = $currentstock - $movements_prod[$objp->rowid]; 504 $nbofmovement = $movements_prod_nb[$objp->rowid]; 505 } 506 } 507 508 509 print '<tr class="oddeven">'; 510 511 // Product ref 512 print '<td class="nowrap">'.$prod->getNomUrl(1, '').'</td>'; 513 514 // Product label 515 print '<td>'.$objp->label; 516 print '<input type="hidden" name="desc'.$i.'" value="'.dol_escape_htmltag($objp->description).'">'; // TODO Remove this and make a fetch to get description when creating order instead of a GETPOST 517 print '</td>'; 518 519 if ($mode == 'future') { 520 // Current stock 521 print '<td class="right">'.$currentstock.'</td>'; 522 523 print '<td class="right"></td>'; 524 525 // Virtual stock at date 526 print '<td class="right">'.$stock.'</td>'; 527 528 // Final virtual stock 529 print '<td class="right">'.$virtualstock.'</td>'; 530 } else { 531 // Stock at date 532 print '<td class="right">'.($stock ? $stock : '<span class="opacitymedium">'.$stock.'</span>').'</td>'; 533 534 print '<td class="right">'; 535 if ($nbofmovement > 0) { 536 print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?idproduct='.$objp->rowid.($fk_warehouse > 0 ? '&search_warehouse='.$fk_warehouse : '').'">'.$langs->trans("Movements").'</a>'; 537 print ' <span class="tabs"><span class="badge">'.$nbofmovement.'</span></span>'; 538 } 539 print '</td>'; 540 541 // Current stock 542 print '<td class="right">'.($currentstock ? $currentstock : '<span class="opacitymedium">0</span>').'</td>'; 543 } 544 545 // Action 546 print '<td class="right"></td>'; 547 548 // Fields from hook 549 $parameters = array('objp'=>$objp); 550 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters); // Note that $action and $object may have been modified by hook 551 print $hookmanager->resPrint; 552 553 print '</tr>'; 554 } 555 $i++; 556} 557 558$parameters = array('sql'=>$sql); 559$reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters); // Note that $action and $object may have been modified by hook 560print $hookmanager->resPrint; 561 562if (empty($date) || ! $dateIsValid) { 563 $colspan = 6; 564 if ($mode == 'future') $colspan++; 565 print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("EnterADateCriteria").'</span></td></tr>'; 566} 567 568print '</table>'; 569print '</div>'; 570 571$db->free($resql); 572 573print dol_get_fiche_end(); 574 575print '</form>'; 576 577llxFooter(); 578 579$db->close(); 580