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&oacute;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">&nbsp;</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">&nbsp;</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