dolibarr 24.0.2
stockatdate.php
Go to the documentation of this file.
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-2025 Frédéric France <frederic.france@free.fr>
8 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
9 * Copyright (C) 2026 Jose MARTINEZ <jose.martinez@pichinov.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
31// Load Dolibarr environment
32require '../../main.inc.php';
40require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
41require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
42require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
43require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
44require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
45require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
46require_once './lib/replenishment.lib.php';
47
48// Load translation files required by the page
49$langs->loadLangs(array('products', 'stocks', 'orders'));
50
51// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
52$hookmanager->initHooks(array('stockatdate'));
53
54//checks if a product has been ordered
55
56$action = GETPOST('action', 'aZ09');
57$type = GETPOSTINT('type');
58$mode = GETPOST('mode', 'alpha');
59
60$ext = (GETPOSTISSET('output') && in_array(GETPOST('output'), array('csv'))) ? GETPOST('output') : '';
61
62$date = '';
63$dateendofday = '';
64if (GETPOSTISSET('dateday') && GETPOSTISSET('datemonth') && GETPOSTISSET('dateyear')) {
65 $date = dol_mktime(0, 0, 0, GETPOSTINT('datemonth'), GETPOSTINT('dateday'), GETPOSTINT('dateyear'));
66 $dateendofday = dol_mktime(23, 59, 59, GETPOSTINT('datemonth'), GETPOSTINT('dateday'), GETPOSTINT('dateyear'));
67}
68
69$search_ref = GETPOST('search_ref', 'alphanohtml');
70$search_nom = GETPOST('search_nom', 'alphanohtml');
71
72$now = dol_now();
73
74$productid = GETPOSTINT('productid');
75if (GETPOSTISARRAY('search_fk_warehouse')) {
76 $search_fk_warehouse = GETPOST('search_fk_warehouse', 'array:int');
77} else {
78 $search_fk_warehouse = array(GETPOSTINT('search_fk_warehouse'));
79}
80// For backward compatibility
81if (GETPOSTINT('fk_warehouse')) {
82 $search_fk_warehouse = array(GETPOSTINT('fk_warehouse'));
83}
84// Clean value -1
85foreach ($search_fk_warehouse as $key => $val) {
86 if ($val == -1 || empty($val)) {
87 unset($search_fk_warehouse[$key]);
88 }
89}
90
91$sortfield = GETPOST('sortfield', 'aZ09comma');
92$sortorder = GETPOST('sortorder', 'aZ09comma');
93$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page");
94if (empty($page) || $page == -1) {
95 $page = 0;
96} // If $page is not defined, or '' or -1
97$limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit;
98$offset = $limit * $page;
99if (!$sortfield) {
100 $sortfield = 'p.ref';
101}
102if (!$sortorder) {
103 $sortorder = 'ASC';
104}
105
106$object = new Entrepot($db);
107
108// Security check
109if ($user->socid) {
110 $socid = $user->socid;
111}
112
113$result = restrictedArea($user, 'produit|service'); // Must have permission to read product
114$result = restrictedArea($user, 'stock'); // Must have permission to read stock
115
116$usercancreadsupplierprice = getDolGlobalString('MAIN_USE_ADVANCED_PERMS') ? $user->hasRight('product', 'product_advance', 'read_supplier_prices') : $user->hasRight('product', 'read');
117
118$dateIsValid = true;
119if ($mode == 'future') {
120 if ($date && $date < $now) {
121 setEventMessages($langs->trans("ErrorDateMustBeInFuture"), null, 'errors');
122 $dateIsValid = false;
123 }
124} else {
125 if ($date && $date > $now) {
126 setEventMessages($langs->trans("ErrorDateMustBeBeforeToday"), null, 'errors');
127 $dateIsValid = false;
128 }
129}
130
131
132/*
133 * Actions
134 */
135
136$parameters = array();
137$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
138if ($reshook < 0) {
139 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
140}
141
142if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // Both test are required to be compatible with all browsers
143 $date = '';
144 $productid = 0;
145 $search_fk_warehouse = array();
146 $search_ref = '';
147 $search_nom = '';
148}
149
150$warehouseStatus = array();
151if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
152 //$warehouseStatus[] = Entrepot::STATUS_CLOSED;
153 $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
154 $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
155}
156
157// Get array with current stock per product, warehouse
158$stock_prod_warehouse = array();
159$stock_prod = array();
160if ($date && $dateIsValid) { // Avoid heavy sql if mandatory date is not defined
161 $sql = "SELECT ps.fk_product, ps.fk_entrepot as fk_warehouse,";
162 $sql .= " SUM(ps.reel) AS stock";
163 $sql .= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
164 $sql .= ", ".MAIN_DB_PREFIX."entrepot as w";
165 $sql .= ", ".MAIN_DB_PREFIX."product as p";
166 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
167 $sql .= " AND w.rowid = ps.fk_entrepot AND p.rowid = ps.fk_product";
168 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS') && count($warehouseStatus)) {
169 $sql .= " AND w.statut IN (".$db->sanitize(implode(',', $warehouseStatus)).")";
170 }
171 if ($productid > 0) {
172 $sql .= " AND ps.fk_product = ".((int) $productid);
173 }
174 if (! empty($search_fk_warehouse)) {
175 $sql .= " AND ps.fk_entrepot IN (".$db->sanitize(implode(",", $search_fk_warehouse)).")";
176 }
177 if ($search_ref) {
178 $sql .= natural_search("p.ref", $search_ref);
179 }
180 if ($search_nom) {
181 $sql .= natural_search("p.label", $search_nom);
182 }
183 $sql .= " GROUP BY fk_product, fk_entrepot";
184 //print $sql;
185
186 $resql = $db->query($sql);
187 if ($resql) {
188 $num = $db->num_rows($resql);
189 $i = 0;
190
191 while ($i < $num) {
192 $obj = $db->fetch_object($resql);
193
194 $tmp_fk_product = $obj->fk_product;
195 $tmp_fk_warehouse = $obj->fk_warehouse;
196 $stock = $obj->stock;
197
198 $stock_prod_warehouse[$tmp_fk_product][$tmp_fk_warehouse] = $stock;
199 $stock_prod[$tmp_fk_product] = (isset($stock_prod[$tmp_fk_product]) ? $stock_prod[$tmp_fk_product] : 0) + $stock;
200
201 $i++;
202 }
203
204 $db->free($resql);
205 $resql = null; // so the free() at the end of the page does not run on an already closed result
206 } else {
208 }
209 //var_dump($stock_prod_warehouse);
210} elseif ($action == 'filter') { // Test on permissions not required here
211 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Date")), null, 'errors');
212}
213
214// Get array with list of stock movements between date and now (for product/warehouse=
215$movements_prod_warehouse = array();
216$movements_prod = array();
217$movements_prod_warehouse_nb = array();
218$movements_prod_nb = array();
219if ($date && $dateIsValid) {
220 $sql = "SELECT sm.fk_product, sm.fk_entrepot, SUM(sm.value) AS stock, COUNT(sm.rowid) AS nbofmovement";
221 $sql .= " FROM ".MAIN_DB_PREFIX."stock_mouvement as sm";
222 $sql .= ", ".MAIN_DB_PREFIX."entrepot as w";
223 $sql .= ", ".MAIN_DB_PREFIX."product as p";
224 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
225 $sql .= " AND w.rowid = sm.fk_entrepot AND p.rowid = sm.fk_product ";
226 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS') && count($warehouseStatus)) {
227 $sql .= " AND w.statut IN (".$db->sanitize(implode(',', $warehouseStatus)).")";
228 }
229 if ($mode == 'future') {
230 $sql .= " AND sm.datem <= '".$db->idate($dateendofday)."'";
231 } else {
232 $sql .= " AND sm.datem >= '".$db->idate($dateendofday)."'";
233 }
234 if ($productid > 0) {
235 $sql .= " AND sm.fk_product = ".((int) $productid);
236 }
237 if (!empty($search_fk_warehouse)) {
238 $sql .= " AND sm.fk_entrepot IN (".$db->sanitize(implode(",", $search_fk_warehouse)).")";
239 }
240 if ($search_ref) {
241 $sql .= " AND p.ref LIKE '%".$db->escape($search_ref)."%' ";
242 }
243 if ($search_nom) {
244 $sql .= " AND p.label LIKE '%".$db->escape($search_nom)."%' ";
245 }
246 $sql .= " GROUP BY sm.fk_product, sm.fk_entrepot";
247
248 $resql = $db->query($sql);
249
250 if ($resql) {
251 $num = $db->num_rows($resql);
252 $i = 0;
253
254 while ($i < $num) {
255 $obj = $db->fetch_object($resql);
256 $fk_product = $obj->fk_product;
257 $fk_entrepot = $obj->fk_entrepot;
258 $stock = $obj->stock;
259 $nbofmovement = $obj->nbofmovement;
260
261 // Pour llx_product_stock.reel
262 $movements_prod_warehouse[$fk_product][$fk_entrepot] = $stock;
263 $movements_prod_warehouse_nb[$fk_product][$fk_entrepot] = $nbofmovement;
264
265 // Pour llx_product.stock
266 $movements_prod[$fk_product] = $stock + (array_key_exists($fk_product, $movements_prod) ? $movements_prod[$fk_product] : 0);
267 $movements_prod_nb[$fk_product] = $nbofmovement + (array_key_exists($fk_product, $movements_prod_nb) ? $movements_prod_nb[$fk_product] : 0);
268
269 $i++;
270 }
271
272 $db->free($resql);
273 $resql = null; // so the free() at the end of the page does not run on an already closed result
274 } else {
276 }
277}
278//var_dump($movements_prod_warehouse);
279//var_dump($movements_prod);
280
281
282/*
283 * View
284 */
285
286$form = new Form($db);
287$formproduct = new FormProduct($db);
288$prod = new Product($db);
289
290$num = 0;
291
292$title = $langs->trans('StockAtDate');
293
294$sql = 'SELECT p.rowid, p.ref, p.label, p.description, p.price, p.pmp,';
295$sql .= ' p.price_ttc, p.price_base_type, p.fk_product_type, p.desiredstock, p.seuil_stock_alerte,';
296$sql .= ' p.tms as datem, p.duration, p.tobuy, p.stock, ';
297if (!empty($search_fk_warehouse)) {
298 $sql .= " SUM(p.pmp * ps.reel) as currentvalue, SUM(p.price * ps.reel) as sellvalue";
299 $sql .= ', SUM(ps.reel) as stock_reel';
300} else {
301 $sql .= " SUM(p.pmp * p.stock) as currentvalue, SUM(p.price * p.stock) as sellvalue";
302}
303// Add fields from hooks
304$parameters = array();
305$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
306$sql .= $hookmanager->resPrint;
307
308$sqlfields = $sql;
309
310$sql .= ' FROM '.MAIN_DB_PREFIX.'product as p';
311if (!empty($search_fk_warehouse)) {
312 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON p.rowid = ps.fk_product AND ps.fk_entrepot IN ('.$db->sanitize(implode(",", $search_fk_warehouse)).")";
313}
314// Add fields from hooks
315$parameters = array();
316$reshook = $hookmanager->executeHooks('printFieldListJoin', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
317$sql .= $hookmanager->resPrint;
318$sql .= ' WHERE p.entity IN ('.getEntity('product').')';
319if ($productid > 0) {
320 $sql .= " AND p.rowid = ".((int) $productid);
321}
322if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
323 $sql .= " AND p.fk_product_type = 0";
324}
325if ($search_ref) {
326 $sql .= natural_search('p.ref', $search_ref);
327}
328if ($search_nom) {
329 $sql .= natural_search('p.label', $search_nom);
330}
331
332$sqlGroupBy = ' GROUP BY p.rowid, p.ref, p.label, p.description, p.price, p.pmp, p.price_ttc, p.price_base_type, p.fk_product_type, p.desiredstock, p.seuil_stock_alerte,';
333$sqlGroupBy .= ' p.tms, p.duration, p.tobuy, p.stock';
334
335$parameters = array('sqlGroupBy' => $sqlGroupBy);
336$reshook = $hookmanager->executeHooks('printFieldListGroupBy', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
337
338if ($reshook == 0) {
339 // Allows the hook to add things (old behavior)
340 $sql .= $hookmanager->resPrint;
341 // Allows the hook to REPLACE the clause (new behavior)
342 if (!empty($hookmanager->resArray['sqlGroupBy'])) {
343 $sqlGroupBy = $hookmanager->resArray['sqlGroupBy'];
344 }
345}
346
347$sql .= $sqlGroupBy;
348
349// Add where from hooks
350$parameters = array();
351$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
352$sql .= $hookmanager->resPrint;
353
354if ($sortfield == 'stock_reel' && empty($search_fk_warehouse)) {
355 $sortfield = 'stock';
356}
357if ($sortfield == 'stock' && !empty($search_fk_warehouse)) {
358 $sortfield = 'stock_reel';
359}
360$sql .= $db->order($sortfield, $sortorder);
361
362// Count total nb of records
363$nbtotalofrecords = '';
364if ($date && $dateIsValid) { // We avoid a heavy sql if mandatory parameter date not yet defined
365 if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
366 /* The fast and low memory method to get and count full list converts the sql into a sql count */
367 $sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
368 $sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
369
370 $resql = $db->query($sqlforcount);
371 if ($resql) {
372 $objforcount = $db->fetch_object($resql);
373 $nbtotalofrecords = $objforcount->nbtotalofrecords;
374 } else {
376 }
377
378 if (($page * $limit) > (int) $nbtotalofrecords || $ext == 'csv') { // if total resultset is smaller than the paging size (filtering), goto and load page 0
379 $page = 0;
380 $offset = 0;
381 }
382 $db->free($resql);
383 }
384
385 //print $sql;
386 if ($ext != 'csv') {
387 $sql .= $db->plimit($limit + 1, $offset);
388 } else {
389 $limit = 0;
390 }
391 $resql = $db->query($sql);
392 if (empty($resql)) {
394 exit;
395 }
396
397 $num = $db->num_rows($resql);
398}
399
400$i = 0;
401
402$helpurl = 'EN:Module_Stocks_En|FR:Module_Stock|';
403$helpurl .= 'ES:M&oacute;dulo_Stocks';
404
405$stocklabel = $langs->trans('StockAtDate');
406if ($mode == 'future') {
407 $stocklabel = $langs->trans("VirtualStockAtDate");
408}
409
410// TODO Move this action into a separated files: We should not mix output with MIME type HTML and MIME type CSV in the same file.
411if ($ext == 'csv') {
412 top_httphead("text/csv");
413 //header("Content-Type: text/csv");
414 header("Content-Disposition: attachment; filename=stock".($date ? '-'.date("Y-m-d", $date) : '').".csv");
415
416 // Lines of title
417 print implode(";", ($mode == 'future') ?
418 array('"Product Reference"', '"Label"', '"Current Stock"', '"'.$stocklabel.'"', '"Virtual Stock"') :
419 array('"Product Reference"', '"Label"', '"'.$stocklabel.'"', ($usercancreadsupplierprice ?'"Estimated Stock Value"' : '""'), '"Estimate Sell Value"', '"Movements"', '"Current Stock"'))."\r\n";
420} else {
421 llxHeader('', $title, $helpurl, '', 0, 0, '', '', '', 'mod-product page-stock_stockatdate');
422
423 $head = array();
424
425 $head[0][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php';
426 $head[0][1] = $langs->trans("StockAtDateInPast");
427 $head[0][2] = 'stockatdatepast';
428
429 $head[1][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php?mode=future';
430 $head[1][1] = $langs->trans("StockAtDateInFuture");
431 $head[1][2] = 'stockatdatefuture';
432
433
434 print load_fiche_titre($langs->trans('StockAtDate'), '', 'stock');
435
436 print dol_get_fiche_head($head, ($mode == 'future' ? 'stockatdatefuture' : 'stockatdatepast'), '', -1, '');
437
438 $desc = $langs->trans("StockAtDatePastDesc");
439 if ($mode == 'future') {
440 $desc = $langs->trans("StockAtDateFutureDesc");
441 }
442 print '<span class="opacitymedium">'.$desc.'</span><br>'."\n";
443 print '<br>'."\n";
444
445 print '<form name="formFilterWarehouse" method="POST" action="'.dolBuildUrl($_SERVER["PHP_SELF"]).'">';
446 print '<input type="hidden" name="token" value="'.newToken().'">';
447 print '<input type="hidden" name="action" value="filter">';
448 print '<input type="hidden" name="mode" value="'.$mode.'">';
449
450 print '<div class="inline-block valignmiddle" style="padding-right: 20px;">';
451 print '<span class="fieldrequired">'.$langs->trans('Date').'</span> '.$form->selectDate(($date ? $date : -1), 'date');
452
453 print ' <span class="clearbothonsmartphone marginleftonly paddingleftonly marginrightonly paddingrightonly">&nbsp;</span> ';
454 print img_picto('', 'product', 'class="pictofixedwidth"').' ';
455 print '</span> ';
456 print $form->select_produits($productid, 'productid', '', 0, 0, -1, 2, '', 0, array(), 0, $langs->trans('Product'), 0, 'maxwidth300', 0, '', null, 1);
457
458 if ($mode != 'future') {
459 // A virtual stock in future has no sense on a per warehouse view, so no filter on warehouse is available for stock at date in future
460 print ' <span class="clearbothonsmartphone marginleftonly paddingleftonly marginrightonly paddingrightonly">&nbsp;</span> ';
461 print img_picto('', 'stock', 'class="pictofixedwidth"').$langs->trans("Warehouse").' :';
462 print '</span> ';
463 $selected = ((GETPOSTISSET('search_fk_warehouse') || GETPOSTISSET('fk_warehouse')) ? $search_fk_warehouse : 'ifonenodefault');
464 print $formproduct->selectWarehouses($selected, 'search_fk_warehouse', '', 1, 0, 0, $langs->trans('Warehouse'), 0, 0, array(), 'minwidth200', array(), 1, false, 'e.ref', 1);
465 }
466
467 print '</div>';
468
469 $parameters = array();
470 $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook
471 if (empty($reshook)) {
472 print $hookmanager->resPrint;
473 }
474
475 print '<div class="inline-block valignmiddle">';
476 print '<input type="submit" class="button" name="valid" value="'.$langs->trans('Refresh').'">';
477 print '</div>';
478
479 //print '</form>';
480
481 $param = '';
482 if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
483 $param .= '&contextpage='.urlencode($contextpage);
484 }
485 if ($limit > 0 && $limit != $conf->liste_limit) {
486 $param .= '&limit='.((int) $limit);
487 }
488 $param .= '&mode='.$mode;
489 $param_warehouse = '';
490 if (!empty($search_fk_warehouse)) {
491 foreach ($search_fk_warehouse as $val) {
492 $param_warehouse .= '&search_fk_warehouse[]='.$val;
493 }
494 $param .= $param_warehouse;
495 }
496 if ($productid > 0) {
497 $param .= '&productid='.(int) $productid;
498 }
499 if (GETPOSTINT('dateday') > 0) {
500 $param .= '&dateday='.GETPOSTINT('dateday');
501 }
502 if (GETPOSTINT('datemonth') > 0) {
503 $param .= '&datemonth='.GETPOSTINT('datemonth');
504 }
505 if (GETPOSTINT('dateyear') > 0) {
506 $param .= '&dateyear='.GETPOSTINT('dateyear');
507 }
508
509 // TODO Move this into the title line ?
510 print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'stock', 0, '', '', $limit, 0, 0, 1);
511
512 print '<div class="div-table-responsive">'; // You can use div-table-responsive-no-min if you don't need reserved height for your table
513 if ($num) {
514 print '<p>';
515 print '<a href="stockatdate.php?output=csv&sortfield='.urlencode($sortfield).'&sortorder='.urlencode($sortorder).'&type='.((int) $type).'&mode='.urlencode($mode).
516 (($productid > 0) ? "&productid=".((int) $productid) : '').
517 $param_warehouse.
518 "&search_ref=".dol_escape_htmltag($search_ref).
519 "&search_nom=".dol_escape_htmltag($search_nom).
520 (GETPOSTISSET('dateday') ? "&dateday=".GETPOSTINT('dateday') : '').
521 (GETPOSTISSET('datemonth') ? "&datemonth=".GETPOSTINT('datemonth') : '').
522 (GETPOSTISSET('dateyear') ? "&dateyear=".GETPOSTINT('dateyear') : '').
523 '" title="Download CSV" />';
524 print img_picto('', 'download', 'class="pictofixedwidth"');
525 print 'Download CSV';
526 print '</a>';
527 print '</p>';
528 }
529 print '<table class="liste centpercent">';
530
531 print '<input type="hidden" name="token" value="'.newToken().'">';
532 print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
533 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
534 print '<input type="hidden" name="type" value="'.$type.'">';
535 print '<input type="hidden" name="mode" value="'.$mode.'">';
536
537 // Fields title search
538 print '<tr class="liste_titre_filter">';
539 if ($conf->main_checkbox_left_column) {
540 print '<td class="liste_titre center maxwidthsearch">';
541 $searchpicto = $form->showFilterButtons('left');
542 print $searchpicto;
543 print '</td>';
544 }
545 print '<td class="liste_titre"><input class="flat" type="text" name="search_ref" size="8" value="'.dol_escape_htmltag($search_ref).'"></td>';
546 print '<td class="liste_titre"><input class="flat" type="text" name="search_nom" size="8" value="'.dol_escape_htmltag($search_nom).'"></td>';
547 print '<td class="liste_titre"></td>';
548 print '<td class="liste_titre"></td>';
549 print '<td class="liste_titre"></td>';
550 if ($mode == 'future') {
551 print '<td class="liste_titre"></td>';
552 } else {
553 print '<td class="liste_titre"></td>';
554 print '<td class="liste_titre"></td>';
555 }
556 // Fields from hook
557 $parameters = array('param' => $param, 'sortfield' => $sortfield, 'sortorder' => $sortorder);
558 $reshook = $hookmanager->executeHooks('printFieldListOption', $parameters); // Note that $action and $object may have been modified by hook
559 print $hookmanager->resPrint;
560
561 // Action column
562 if (!$conf->main_checkbox_left_column) {
563 print '<td class="liste_titre center maxwidthsearch">';
564 $searchpicto = $form->showFilterButtons();
565 print $searchpicto;
566 print '</td>';
567 }
568 print '</tr>';
569
570 $fieldtosortcurrentstock = 'stock';
571 if (!empty($search_fk_warehouse)) {
572 $fieldtosortcurrentstock = 'stock_reel';
573 }
574
575 // Lines of title
576 print '<tr class="liste_titre">';
577 // Action column
578 if ($conf->main_checkbox_left_column) {
579 print_liste_field_titre('', $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, 'left ');
580 }
581 print_liste_field_titre('ProductRef', $_SERVER["PHP_SELF"], 'p.ref', '', $param, '', $sortfield, $sortorder);
582 print_liste_field_titre('Label', $_SERVER["PHP_SELF"], 'p.label', '', $param, '', $sortfield, $sortorder);
583
584 if ($mode == 'future') {
585 print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, '', $param, '', $sortfield, $sortorder, 'right ');
586 print_liste_field_titre('', $_SERVER["PHP_SELF"]);
587 print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, 'right ', 'VirtualStockAtDateDesc');
588 print_liste_field_titre('VirtualStock', $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, 'right ', 'VirtualStockDesc');
589 } else {
590 print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, 'right ');
591 $tooltiptext = $langs->trans("QtyAtDate").' x '.$langs->trans("AverageUnitPricePMPShort").' ('.$langs->trans("Currently").')';
592 if ($usercancreadsupplierprice) {
593 print_liste_field_titre("EstimatedStockValue", $_SERVER["PHP_SELF"], "currentvalue", '', $param, '', $sortfield, $sortorder, 'right ', $tooltiptext, 1);
594 }
595 $tooltiptext = $langs->trans("QtyAtDate").' x '.$langs->trans("SellingPrice").' ('.$langs->trans("Currently").')';
596 print_liste_field_titre("EstimatedStockValueSell", $_SERVER["PHP_SELF"], "", '', $param, '', $sortfield, $sortorder, 'right ', $tooltiptext, 1);
597 $tooltiptext = $langs->trans("MovementsSinceDate");
598 print_liste_field_titre('since', $_SERVER["PHP_SELF"], '', '', $param, '', '', '', 'right ', $tooltiptext, 1);
599 print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, '', $param, '', $sortfield, $sortorder, 'right ');
600 }
601
602 // Hook fields
603 $parameters = array('param' => $param, 'sortfield' => $sortfield, 'sortorder' => $sortorder);
604 $reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
605 print $hookmanager->resPrint;
606
607 // Action column
608 if (!$conf->main_checkbox_left_column) {
609 print_liste_field_titre('', $_SERVER["PHP_SELF"], '', '', $param, '', $sortfield, $sortorder, 'right ');
610 }
611
612 print "</tr>\n";
613}
614
615$totalbuyingprice = 0;
616$totalsellingprice = 0;
617$totalcurrentstock = 0;
618$totalvirtualstock = 0;
619
620$i = 0;
621while ($i < ($limit ? min($num, $limit) : $num)) {
622 if (empty($resql)) {
623 break;
624 }
625
626 $objp = $db->fetch_object($resql);
627
628 if (getDolGlobalString('STOCK_SUPPORTS_SERVICES') || $objp->fk_product_type == 0) {
629 $prod->fetch($objp->rowid);
630
631 // Multilangs
632 /*if (getDolGlobalInt('MAIN_MULTILANGS'))
633 {
634 $sql = 'SELECT label,description';
635 $sql .= ' FROM '.MAIN_DB_PREFIX.'product_lang';
636 $sql .= ' WHERE fk_product = '.((int) $objp->rowid);
637 $sql .= " AND lang = '".$db->escape($langs->getDefaultLang())."'";
638 $sql .= ' LIMIT 1';
639
640 $resqlm = $db->query($sql);
641 if ($resqlm)
642 {
643 $objtp = $db->fetch_object($resqlm);
644 if (!empty($objtp->description)) $objp->description = $objtp->description;
645 if (!empty($objtp->label)) $objp->label = $objtp->label;
646 }
647 }*/
648
649 $currentstock = '';
650 if (!empty($search_fk_warehouse)) {
651 //if ($productid > 0) {
652 foreach ($search_fk_warehouse as $val) {
653 if (!is_numeric($currentstock)) {
654 $currentstock = 0;
655 }
656 $currentstock += empty($stock_prod_warehouse[$objp->rowid][$val]) ? 0 : $stock_prod_warehouse[$objp->rowid][$val];
657 }
658 //} else {
659 // $currentstock = $objp->stock_reel;
660 //}
661 } else {
662 //if ($productid > 0) {
663 $currentstock = empty($stock_prod[$objp->rowid]) ? 0 : $stock_prod[$objp->rowid];
664 //} else {
665 // $currentstock = $objp->stock;
666 //}
667 }
668
669 $nbofmovement = 0;
670 $virtualstock = 0;
671 if ($mode == 'future') {
672 $prod->load_stock('warehouseopen,warehouseinternal,nobatch', 0, $dateendofday);
673 $stock = $prod->stock_theorique; // virtual stock at a date
674 $prod->load_stock('warehouseopen,warehouseinternal,nobatch', 0);
675 $virtualstock = $prod->stock_theorique; // virtual stock in infinite future
676 } else {
677 $stock = $currentstock;
678 if (!empty($search_fk_warehouse)) {
679 foreach ($search_fk_warehouse as $val) {
680 $stock -= empty($movements_prod_warehouse[$objp->rowid][$val]) ? 0 : $movements_prod_warehouse[$objp->rowid][$val];
681 $nbofmovement += empty($movements_prod_warehouse_nb[$objp->rowid][$val]) ? 0 : $movements_prod_warehouse_nb[$objp->rowid][$val];
682 }
683 } else {
684 $stock -= empty($movements_prod[$objp->rowid]) ? 0 : $movements_prod[$objp->rowid];
685 $nbofmovement += empty($movements_prod_nb[$objp->rowid]) ? 0 : $movements_prod_nb[$objp->rowid];
686 }
687 }
688
689
690 if ($ext == 'csv') {
691 if ($mode == 'future') {
692 print implode(";", array(
693 '"'.$objp->ref.'"',
694 '"'.$objp->label.'"',
695 '"'.price2num($currentstock, 'MS').'"',
696 '"'.price2num($stock, 'MS').'"',
697 '"'.price2num($virtualstock, 'MS').'"'))."\r\n";
698 $totalvirtualstock += $virtualstock;
699 } else {
700 print implode(";", array(
701 '"'.$objp->ref.'"',
702 '"'.$objp->label.'"',
703 '"'.price(price2num($stock, 'MS')).'"',
704 ($usercancreadsupplierprice ? (price2num($stock * $objp->pmp, 'MT') ? '"'.price2num($stock * $objp->pmp, 'MT').'"' : '') : ''),
705 (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) ? '"'.price2num($stock * $objp->price, 'MT').'"' : '"'.$langs->trans("Variable").'('.$langs->trans("OptionMULTIPRICESIsOn").')"',
706 "$nbofmovement",
707 '"'.price2num($currentstock, 'MS').'"'))."\r\n";
708 if ($usercancreadsupplierprice) {
709 $totalbuyingprice += $stock * $objp->pmp;
710 }
711 $totalsellingprice += $stock * $objp->price;
712 }
713 $totalcurrentstock += $currentstock;
714 } else {
715 print '<tr class="oddeven">';
716
717 // Action column
718 if ($conf->main_checkbox_left_column) {
719 print '<td class="left"></td>';
720 }
721
722 // Product ref
723 print '<td class="nowrap">'.$prod->getNomUrl(1, '').'</td>';
724
725 // Product label
726 print '<td>';
727 print dol_escape_htmltag($objp->label);
728 print '</td>';
729
730 if ($mode == 'future') {
731 // Current stock
732 print '<td class="right">'.price(price2num($currentstock, 'MS')).'</td>';
733 //$totalcurrentstock += $currentstock;
734
735 print '<td class="right"></td>';
736
737 // Virtual stock at date
738 print '<td class="right">'.price(price2num($stock, 'MS')).'</td>';
739
740 // Final virtual stock
741 print '<td class="right">'.price(price2num($virtualstock, 'MS')).'</td>';
742 $totalvirtualstock += $virtualstock;
743 } else {
744 // Stock at date
745 print '<td class="right">'.($stock ? price(price2num($stock, 'MS')) : ('<span class="opacitymedium">0</span>')).'</td>';
746
747 // PMP value
748 $estimatedvalue = $stock * $objp->pmp;
749 if ($usercancreadsupplierprice) {
750 print '<td class="right" title="'.dolPrintHTMLForAttribute($langs->trans("AverageUnitPricePMPShort").' ('.$langs->trans("Currently").'): '.price(price2num($objp->pmp, 'MU'), 1)).'">';
751 if (price2num($estimatedvalue, 'MT')) {
752 print '<span class="amount">'.price(price2num($estimatedvalue, 'MT'), 1).'</span>';
753 } else {
754 print '';
755 }
756 $totalbuyingprice += $estimatedvalue;
757 print '</td>';
758 }
759
760 // Selling value
761 print '<td class="right"';
762 if (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
763 print ' title="'.dolPrintHTMLForAttribute($langs->trans("SellingPrice").' ('.$langs->trans("Currently").'): '.price(price2num($objp->price, 'MU'), 1));
764 }
765 print '">';
766 if (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
767 print '<span class="amount">';
768 if ($stock || (float) ($stock * $objp->price)) {
769 print price(price2num($stock * $objp->price, 'MT'), 1);
770 }
771 print '</span>';
772 $totalsellingprice += $stock * $objp->price;
773 } else {
774 $htmltext = $langs->trans("OptionMULTIPRICESIsOn");
775 print $form->textwithtooltip('<span class="opacitymedium">'.$langs->trans("Variable").'</span>', $htmltext);
776 }
777 print '</td>';
778
779 // Movements
780 print '<td class="right">';
781 if ($nbofmovement > 0) {
782 $url = DOL_URL_ROOT.'/product/stock/movement_list.php?idproduct='.$objp->rowid;
783 if (GETPOSTISSET('datemonth')) {
784 $url .= '&search_date_startday='.GETPOSTINT('dateday');
785 $url .= '&search_date_startmonth='.GETPOSTINT('datemonth');
786 $url .= '&search_date_startyear='.GETPOSTINT('dateyear');
787 }
788 if (count($search_fk_warehouse) > 1) {
789 $url = ''; // Do not show link, multi warehouse as filter not managed yet by target page
790 } else {
791 foreach ($search_fk_warehouse as $val) {
792 $url .= ($val > 0 ? '&search_warehouse='.((int) $val) : '');
793 }
794 }
795 if ($url) {
796 print '<a href="'.$url.'">';
797 }
798 print $langs->trans("Movements");
799 print '<span class="tabs paddingleft"><span class="badge">'.$nbofmovement.'</span></span>';
800 if ($url) {
801 print '</a>';
802 }
803 }
804 print '</td>';
805
806 // Current stock
807 print '<td class="right">'.($currentstock ? price(price2num($currentstock, 'MS')) : '<span class="opacitymedium">0</span>').'</td>';
808 }
809 $totalcurrentstock += $currentstock;
810
811 // Fields from hook
812 $parameters = array('objp' => $objp);
813 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters); // Note that $action and $object may have been modified by hook
814 print $hookmanager->resPrint;
815
816 // Action column
817 if (!$conf->main_checkbox_left_column) {
818 print '<td class="right"></td>';
819 }
820
821 print '</tr>'."\n";
822 }
823 }
824 $i++;
825}
826
827$parameters = array('sql' => $sql);
828$reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters); // Note that $action and $object may have been modified by hook
829if ($ext != 'csv') {
830 print $hookmanager->resPrint;
831}
832
833$colspan = 8;
834if ($mode == 'future') {
835 $colspan++;
836}
837
838if ($ext == 'csv') {
839 print implode(
840 ";",
841 ($mode == 'future') ? array(
842 '"'.$langs->trans("Totalforthispage").'"',
843 '',
844 $productid > 0 ? price2num($totalcurrentstock, 'MS') : '',
845 '',
846 price(price2num($totalvirtualstock, 'MS'))) :
847 array(
848 '"'.$langs->trans("Totalforthispage").'"',
849 '',
850 '',
851 '"'.($usercancreadsupplierprice ? price2num($totalbuyingprice, 'MT') : '').'"',
852 (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) ? '"'.price2num($totalsellingprice, 'MT').'"' : '',
853 '',
854 $productid > 0 ? price2num($totalcurrentstock, 'MS') : '')
855 );
856} else {
857 if (empty($date) || !$dateIsValid) {
858 print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("EnterADateCriteria").'</span></td></tr>';
859 } else {
860 print '<tr class="liste_total">';
861 print '<td>'.$langs->trans("Totalforthispage").'</td>';
862 print '<td></td>';
863 if ($mode == 'future') {
864 print '<td class="right">'.price(price2num($totalcurrentstock, 'MS')).'</td>';
865 print '<td></td>';
866 print '<td></td>';
867 print '<td class="right">'.price(price2num($totalvirtualstock, 'MS')).'</td>';
868 } else {
869 print '<td></td>';
870 if ($usercancreadsupplierprice) {
871 print '<td class="right">'.price(price2num($totalbuyingprice, 'MT')).'</td>';
872 }
873 if (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
874 print '<td class="right">'.price(price2num($totalsellingprice, 'MT')).'</td>';
875 } else {
876 print '<td></td>';
877 }
878 print '<td></td>';
879 print '<td class="right">'.($productid > 0 ? price(price2num($totalcurrentstock, 'MS')) : '').'</td>';
880 }
881 print '<td></td>';
882 print '</tr>';
883 }
884
885 print '</table>';
886 print '</div>';
887
888 print dol_get_fiche_end();
889
890 print '</form>';
891
892 llxFooter();
893}
894
895if (!empty($resql)) {
896 $db->free($resql);
897}
898
899$db->close();
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class to manage warehouses.
const STATUS_OPEN_INTERNAL
Warehouse open and only operations for stock transfers/corrections allowed (not for customer shipping...
const STATUS_OPEN_ALL
Warehouse open and any operations are allowed (customer shipping, supplier dispatch,...
Class to manage generation of HTML components Only common components must be here.
Class with static methods for building HTML components related to products Only components common to ...
Class to manage products or services.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
print_liste_field_titre($name, $file="", $field="", $begin="", $param="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $tooltip="", $forcenowrapcolumntitle=0)
Show title line of an array.
print_barre_liste($title, $page, $file, $options='', $sortfield='', $sortorder='', $morehtmlcenter='', $num=-1, $totalnboflines='', $picto='generic', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limit=-1, $selectlimitsuffix=0, $hidenavigation=0, $pagenavastextinput=0, $morehtmlrightbeforearrow='')
Print a title with navigation controls for pagination.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
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.
GETPOSTISARRAY($paramname, $method=0)
Return true if the parameter $paramname is submit from a POST OR GET as an array.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:492
restrictedArea(User $user, $features, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $isdraft=0, $nodie=0, $mode='')
Check permissions of a user to show a page and an object.