dolibarr 21.0.0-alpha
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-2024 Frédéric France <frederic.france@free.fr>
8 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
30// Load Dolibarr environment
31require '../../main.inc.php';
32require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
33require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
34require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
35require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
36require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
37require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
38require_once './lib/replenishment.lib.php';
39
40// Load translation files required by the page
41$langs->loadLangs(array('products', 'stocks', 'orders'));
42
43// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
44$hookmanager->initHooks(array('stockatdate'));
45
46//checks if a product has been ordered
47
48$action = GETPOST('action', 'aZ09');
49$type = GETPOSTINT('type');
50$mode = GETPOST('mode', 'alpha');
51
52$ext = (GETPOSTISSET('output') && in_array(GETPOST('output'), array('csv'))) ? GETPOST('output') : '';
53
54$date = '';
55$dateendofday = '';
56if (GETPOSTISSET('dateday') && GETPOSTISSET('datemonth') && GETPOSTISSET('dateyear')) {
57 $date = dol_mktime(0, 0, 0, GETPOSTINT('datemonth'), GETPOSTINT('dateday'), GETPOSTINT('dateyear'));
58 $dateendofday = dol_mktime(23, 59, 59, GETPOSTINT('datemonth'), GETPOSTINT('dateday'), GETPOSTINT('dateyear'));
59}
60
61$search_ref = GETPOST('search_ref', 'alphanohtml');
62$search_nom = GETPOST('search_nom', 'alphanohtml');
63
64$now = dol_now();
65
66$productid = GETPOSTINT('productid');
67if (GETPOSTISARRAY('search_fk_warehouse')) {
68 $search_fk_warehouse = GETPOST('search_fk_warehouse', 'array:int');
69} else {
70 $search_fk_warehouse = array(GETPOSTINT('search_fk_warehouse'));
71}
72// For backward compatibility
73if (GETPOSTINT('fk_warehouse')) {
74 $search_fk_warehouse = array(GETPOSTINT('fk_warehouse'));
75}
76// Clean value -1
77foreach ($search_fk_warehouse as $key => $val) {
78 if ($val == -1 || empty($val)) {
79 unset($search_fk_warehouse[$key]);
80 }
81}
82
83$sortfield = GETPOST('sortfield', 'aZ09comma');
84$sortorder = GETPOST('sortorder', 'aZ09comma');
85$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page");
86if (empty($page) || $page == -1) {
87 $page = 0;
88} // If $page is not defined, or '' or -1
89$limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit;
90$offset = $limit * $page;
91if (!$sortfield) {
92 $sortfield = 'p.ref';
93}
94if (!$sortorder) {
95 $sortorder = 'ASC';
96}
97
98$parameters = array();
99$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
100if ($reshook < 0) {
101 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
102}
103
104$dateIsValid = true;
105if ($mode == 'future') {
106 if ($date && $date < $now) {
107 setEventMessages($langs->trans("ErrorDateMustBeInFuture"), null, 'errors');
108 $dateIsValid = false;
109 }
110} else {
111 if ($date && $date > $now) {
112 setEventMessages($langs->trans("ErrorDateMustBeBeforeToday"), null, 'errors');
113 $dateIsValid = false;
114 }
115}
116
117// Security check
118if ($user->socid) {
119 $socid = $user->socid;
120}
121
122$result = restrictedArea($user, 'produit|service'); // Must have permission to read product
123$result = restrictedArea($user, 'stock'); // Must have permission to read stock
124
125
126/*
127 * Actions
128 */
129
130if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // Both test are required to be compatible with all browsers
131 $date = '';
132 $productid = 0;
133 $search_fk_warehouse = array();
134 $search_ref = '';
135 $search_nom = '';
136}
137
138$warehouseStatus = array();
139if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
140 //$warehouseStatus[] = Entrepot::STATUS_CLOSED;
141 $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
142 $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
143}
144
145// Get array with current stock per product, warehouse
146$stock_prod_warehouse = array();
147$stock_prod = array();
148if ($date && $dateIsValid) { // Avoid heavy sql if mandatory date is not defined
149 $sql = "SELECT ps.fk_product, ps.fk_entrepot as fk_warehouse,";
150 $sql .= " SUM(ps.reel) AS stock";
151 $sql .= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
152 $sql .= ", ".MAIN_DB_PREFIX."entrepot as w";
153 $sql .= ", ".MAIN_DB_PREFIX."product as p";
154 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
155 $sql .= " AND w.rowid = ps.fk_entrepot AND p.rowid = ps.fk_product";
156 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS') && count($warehouseStatus)) {
157 $sql .= " AND w.statut IN (".$db->sanitize(implode(',', $warehouseStatus)).")";
158 }
159 if ($productid > 0) {
160 $sql .= " AND ps.fk_product = ".((int) $productid);
161 }
162 if (! empty($search_fk_warehouse)) {
163 $sql .= " AND ps.fk_entrepot IN (".$db->sanitize(implode(",", $search_fk_warehouse)).")";
164 }
165 if ($search_ref) {
166 $sql .= natural_search("p.ref", $search_ref);
167 }
168 if ($search_nom) {
169 $sql .= natural_search("p.label", $search_nom);
170 }
171 $sql .= " GROUP BY fk_product, fk_entrepot";
172 //print $sql;
173
174 $resql = $db->query($sql);
175 if ($resql) {
176 $num = $db->num_rows($resql);
177 $i = 0;
178
179 while ($i < $num) {
180 $obj = $db->fetch_object($resql);
181
182 $tmp_fk_product = $obj->fk_product;
183 $tmp_fk_warehouse = $obj->fk_warehouse;
184 $stock = $obj->stock;
185
186 $stock_prod_warehouse[$tmp_fk_product][$tmp_fk_warehouse] = $stock;
187 $stock_prod[$tmp_fk_product] = (isset($stock_prod[$tmp_fk_product]) ? $stock_prod[$tmp_fk_product] : 0) + $stock;
188
189 $i++;
190 }
191
192 $db->free($resql);
193 } else {
194 dol_print_error($db);
195 }
196 //var_dump($stock_prod_warehouse);
197} elseif ($action == 'filter') { // Test on permissions not required here
198 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Date")), null, 'errors');
199}
200
201// Get array with list of stock movements between date and now (for product/warehouse=
202$movements_prod_warehouse = array();
203$movements_prod = array();
204$movements_prod_warehouse_nb = array();
205$movements_prod_nb = array();
206if ($date && $dateIsValid) {
207 $sql = "SELECT sm.fk_product, sm.fk_entrepot, SUM(sm.value) AS stock, COUNT(sm.rowid) AS nbofmovement";
208 $sql .= " FROM ".MAIN_DB_PREFIX."stock_mouvement as sm";
209 $sql .= ", ".MAIN_DB_PREFIX."entrepot as w";
210 $sql .= ", ".MAIN_DB_PREFIX."product as p";
211 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
212 $sql .= " AND w.rowid = sm.fk_entrepot AND p.rowid = sm.fk_product ";
213 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS') && count($warehouseStatus)) {
214 $sql .= " AND w.statut IN (".$db->sanitize(implode(',', $warehouseStatus)).")";
215 }
216 if ($mode == 'future') {
217 $sql .= " AND sm.datem <= '".$db->idate($dateendofday)."'";
218 } else {
219 $sql .= " AND sm.datem >= '".$db->idate($dateendofday)."'";
220 }
221 if ($productid > 0) {
222 $sql .= " AND sm.fk_product = ".((int) $productid);
223 }
224 if (!empty($search_fk_warehouse)) {
225 $sql .= " AND sm.fk_entrepot IN (".$db->sanitize(implode(",", $search_fk_warehouse)).")";
226 }
227 if ($search_ref) {
228 $sql .= " AND p.ref LIKE '%".$db->escape($search_ref)."%' ";
229 }
230 if ($search_nom) {
231 $sql .= " AND p.label LIKE '%".$db->escape($search_nom)."%' ";
232 }
233 $sql .= " GROUP BY sm.fk_product, sm.fk_entrepot";
234
235 $resql = $db->query($sql);
236
237 if ($resql) {
238 $num = $db->num_rows($resql);
239 $i = 0;
240
241 while ($i < $num) {
242 $obj = $db->fetch_object($resql);
243 $fk_product = $obj->fk_product;
244 $fk_entrepot = $obj->fk_entrepot;
245 $stock = $obj->stock;
246 $nbofmovement = $obj->nbofmovement;
247
248 // Pour llx_product_stock.reel
249 $movements_prod_warehouse[$fk_product][$fk_entrepot] = $stock;
250 $movements_prod_warehouse_nb[$fk_product][$fk_entrepot] = $nbofmovement;
251
252 // Pour llx_product.stock
253 $movements_prod[$fk_product] = $stock + (array_key_exists($fk_product, $movements_prod) ? $movements_prod[$fk_product] : 0);
254 $movements_prod_nb[$fk_product] = $nbofmovement + (array_key_exists($fk_product, $movements_prod_nb) ? $movements_prod_nb[$fk_product] : 0);
255
256 $i++;
257 }
258
259 $db->free($resql);
260 } else {
261 dol_print_error($db);
262 }
263}
264//var_dump($movements_prod_warehouse);
265//var_dump($movements_prod);
266
267
268/*
269 * View
270 */
271
272$form = new Form($db);
273$formproduct = new FormProduct($db);
274$prod = new Product($db);
275
276$num = 0;
277
278$title = $langs->trans('StockAtDate');
279
280$sql = 'SELECT p.rowid, p.ref, p.label, p.description, p.price, p.pmp,';
281$sql .= ' p.price_ttc, p.price_base_type, p.fk_product_type, p.desiredstock, p.seuil_stock_alerte,';
282$sql .= ' p.tms as datem, p.duration, p.tobuy, p.stock, ';
283if (!empty($search_fk_warehouse)) {
284 $sql .= " SUM(p.pmp * ps.reel) as currentvalue, SUM(p.price * ps.reel) as sellvalue";
285 $sql .= ', SUM(ps.reel) as stock_reel';
286} else {
287 $sql .= " SUM(p.pmp * p.stock) as currentvalue, SUM(p.price * p.stock) as sellvalue";
288}
289// Add fields from hooks
290$parameters = array();
291$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters); // Note that $action and $object may have been modified by hook
292$sql .= $hookmanager->resPrint;
293
294$sql .= ' FROM '.MAIN_DB_PREFIX.'product as p';
295if (!empty($search_fk_warehouse)) {
296 $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)).")";
297}
298// Add fields from hooks
299$parameters = array();
300$reshook = $hookmanager->executeHooks('printFieldListJoin', $parameters); // Note that $action and $object may have been modified by hook
301$sql .= $hookmanager->resPrint;
302$sql .= ' WHERE p.entity IN ('.getEntity('product').')';
303if ($productid > 0) {
304 $sql .= " AND p.rowid = ".((int) $productid);
305}
306if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
307 $sql .= " AND p.fk_product_type = 0";
308}
309if (!empty($canvas)) {
310 $sql .= " AND p.canvas = '".$db->escape($canvas)."'";
311}
312if ($search_ref) {
313 $sql .= natural_search('p.ref', $search_ref);
314}
315if ($search_nom) {
316 $sql .= natural_search('p.label', $search_nom);
317}
318$sql .= ' 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,';
319$sql .= ' p.tms, p.duration, p.tobuy, p.stock';
320// Add where from hooks
321$parameters = array();
322$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
323$sql .= $hookmanager->resPrint;
324
325if ($sortfield == 'stock_reel' && empty($search_fk_warehouse)) {
326 $sortfield = 'stock';
327}
328if ($sortfield == 'stock' && !empty($search_fk_warehouse)) {
329 $sortfield = 'stock_reel';
330}
331$sql .= $db->order($sortfield, $sortorder);
332
333$nbtotalofrecords = '';
334if ($date && $dateIsValid) { // We avoid a heavy sql if mandatory parameter date not yet defined
335 if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
336 $result = $db->query($sql);
337 $nbtotalofrecords = $db->num_rows($result);
338 if (($page * $limit) > $nbtotalofrecords || $ext == 'csv') { // if total resultset is smaller then paging size (filtering), goto and load page 0
339 $page = 0;
340 $offset = 0;
341 }
342 }
343
344 //print $sql;
345 if ($ext != 'csv') {
346 $sql .= $db->plimit($limit + 1, $offset);
347 $resql = $db->query($sql);
348 } else {
349 $resql = $result;
350 $limit = 0;
351 }
352 if (empty($resql)) {
353 dol_print_error($db);
354 exit;
355 }
356
357 $num = $db->num_rows($resql);
358}
359
360$i = 0;
361
362$helpurl = 'EN:Module_Stocks_En|FR:Module_Stock|';
363$helpurl .= 'ES:M&oacute;dulo_Stocks';
364
365$stocklabel = $langs->trans('StockAtDate');
366if ($mode == 'future') {
367 $stocklabel = $langs->trans("VirtualStockAtDate");
368}
369
370// 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.
371if ($ext == 'csv') {
372 top_httphead("text/csv");
373 //header("Content-Type: text/csv");
374 header("Content-Disposition: attachment; filename=stock".($date ? '-'.date("Y-m-d", $date) : '').".csv");
375
376 // Lines of title
377 print implode(";", ($mode == 'future') ?
378 array('"Product Reference"', '"Label"', '"Current Stock"', '"'.$stocklabel.'"', '"Virtual Stock"') :
379 array('"Product Reference"', '"Label"', '"'.$stocklabel.'"', '"Estimated Stock Value"', '"Estimate Sell Value"', '"Movements"', '"Current Stock"'))."\r\n";
380} else {
381 llxHeader('', $title, $helpurl, '', 0, 0, '', '', '', 'mod-product page-stock_stockatdate');
382
383 $head = array();
384
385 $head[0][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php';
386 $head[0][1] = $langs->trans("StockAtDateInPast");
387 $head[0][2] = 'stockatdatepast';
388
389 $head[1][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php?mode=future';
390 $head[1][1] = $langs->trans("StockAtDateInFuture");
391 $head[1][2] = 'stockatdatefuture';
392
393
394 print load_fiche_titre($langs->trans('StockAtDate'), '', 'stock');
395
396 print dol_get_fiche_head($head, ($mode == 'future' ? 'stockatdatefuture' : 'stockatdatepast'), '', -1, '');
397
398 $desc = $langs->trans("StockAtDatePastDesc");
399 if ($mode == 'future') {
400 $desc = $langs->trans("StockAtDateFutureDesc");
401 }
402 print '<span class="opacitymedium">'.$desc.'</span><br>'."\n";
403 print '<br>'."\n";
404
405 print '<form name="formFilterWarehouse" method="POST" action="'.$_SERVER["PHP_SELF"].'">';
406 print '<input type="hidden" name="token" value="'.newToken().'">';
407 print '<input type="hidden" name="action" value="filter">';
408 print '<input type="hidden" name="mode" value="'.$mode.'">';
409
410 print '<div class="inline-block valignmiddle" style="padding-right: 20px;">';
411 print '<span class="fieldrequired">'.$langs->trans('Date').'</span> '.$form->selectDate(($date ? $date : -1), 'date');
412
413 print ' <span class="clearbothonsmartphone marginleftonly paddingleftonly marginrightonly paddingrightonly">&nbsp;</span> ';
414 print img_picto('', 'product', 'class="pictofixedwidth"').' ';
415 print '</span> ';
416 print $form->select_produits($productid, 'productid', '', 0, 0, -1, 2, '', 0, array(), 0, $langs->trans('Product'), 0, 'maxwidth300', 0, '', null, 1);
417
418 if ($mode != 'future') {
419 // 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
420 print ' <span class="clearbothonsmartphone marginleftonly paddingleftonly marginrightonly paddingrightonly">&nbsp;</span> ';
421 print img_picto('', 'stock', 'class="pictofixedwidth"').$langs->trans("Warehouse").' :';
422 print '</span> ';
423 $selected = ((GETPOSTISSET('search_fk_warehouse') || GETPOSTISSET('fk_warehouse')) ? $search_fk_warehouse : 'ifonenodefault');
424 print $formproduct->selectWarehouses($selected, 'search_fk_warehouse', '', 1, 0, 0, $langs->trans('Warehouse'), 0, 0, array(), 'minwidth200', array(), 1, false, 'e.ref', 1);
425 }
426
427 print '</div>';
428
429 $parameters = array();
430 $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook
431 if (empty($reshook)) {
432 print $hookmanager->resPrint;
433 }
434
435 print '<div class="inline-block valignmiddle">';
436 print '<input type="submit" class="button" name="valid" value="'.$langs->trans('Refresh').'">';
437 print '</div>';
438
439 //print '</form>';
440
441 $param = '';
442 if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
443 $param .= '&contextpage='.urlencode($contextpage);
444 }
445 if ($limit > 0 && $limit != $conf->liste_limit) {
446 $param .= '&limit='.((int) $limit);
447 }
448 $param .= '&mode='.$mode;
449 $param_warehouse = '';
450 if (!empty($search_fk_warehouse)) {
451 foreach ($search_fk_warehouse as $val) {
452 $param_warehouse .= '&search_fk_warehouse[]='.$val;
453 }
454 $param .= $param_warehouse;
455 }
456 if ($productid > 0) {
457 $param .= '&productid='.(int) $productid;
458 }
459 if (GETPOSTINT('dateday') > 0) {
460 $param .= '&dateday='.GETPOSTINT('dateday');
461 }
462 if (GETPOSTINT('datemonth') > 0) {
463 $param .= '&datemonth='.GETPOSTINT('datemonth');
464 }
465 if (GETPOSTINT('dateyear') > 0) {
466 $param .= '&dateyear='.GETPOSTINT('dateyear');
467 }
468
469 // TODO Move this into the title line ?
470 print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'stock', 0, '', '', $limit, 0, 0, 1);
471
472 print '<div class="div-table-responsive">'; // You can use div-table-responsive-no-min if you don't need reserved height for your table
473 if ($num) {
474 print '<p>';
475 print '<a href="stockatdate.php?output=csv&sortfield='.urlencode($sortfield).'&sortorder='.urlencode($sortorder).'&type='.((int) $type).'&mode='.urlencode($mode).
476 (($productid > 0) ? "&productid=".((int) $productid) : '').
477 $param_warehouse.
478 "&search_ref=".dol_escape_htmltag($search_ref).
479 "&search_nom=".dol_escape_htmltag($search_nom).
480 (GETPOSTISSET('dateday') ? "&dateday=".GETPOSTINT('dateday') : '').
481 (GETPOSTISSET('datemonth') ? "&datemonth=".GETPOSTINT('datemonth') : '').
482 (GETPOSTISSET('dateyear') ? "&dateyear=".GETPOSTINT('dateyear') : '').
483 '" title="Download CSV" />';
484 print img_picto('', 'download', 'class="pictofixedwidth"');
485 print 'Download CSV';
486 print '</a>';
487 print '</p>';
488 }
489 print '<table class="liste centpercent">';
490
491 print '<input type="hidden" name="token" value="'.newToken().'">';
492 print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
493 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
494 print '<input type="hidden" name="type" value="'.$type.'">';
495 print '<input type="hidden" name="mode" value="'.$mode.'">';
496
497 // Fields title search
498 print '<tr class="liste_titre_filter">';
499 print '<td class="liste_titre"><input class="flat" type="text" name="search_ref" size="8" value="'.dol_escape_htmltag($search_ref).'"></td>';
500 print '<td class="liste_titre"><input class="flat" type="text" name="search_nom" size="8" value="'.dol_escape_htmltag($search_nom).'"></td>';
501 print '<td class="liste_titre"></td>';
502 print '<td class="liste_titre"></td>';
503 print '<td class="liste_titre"></td>';
504 if ($mode == 'future') {
505 print '<td class="liste_titre"></td>';
506 } else {
507 print '<td class="liste_titre"></td>';
508 print '<td class="liste_titre"></td>';
509 }
510 // Fields from hook
511 $parameters = array('param' => $param, 'sortfield' => $sortfield, 'sortorder' => $sortorder);
512 $reshook = $hookmanager->executeHooks('printFieldListOption', $parameters); // Note that $action and $object may have been modified by hook
513 print $hookmanager->resPrint;
514
515 print '<td class="liste_titre maxwidthsearch">';
516 $searchpicto = $form->showFilterAndCheckAddButtons(0);
517 print $searchpicto;
518 print '</td>';
519 print '</tr>';
520
521 $fieldtosortcurrentstock = 'stock';
522 if (!empty($search_fk_warehouse)) {
523 $fieldtosortcurrentstock = 'stock_reel';
524 }
525
526 // Lines of title
527 print '<tr class="liste_titre">';
528 print_liste_field_titre('ProductRef', $_SERVER["PHP_SELF"], 'p.ref', $param, '', '', $sortfield, $sortorder);
529 print_liste_field_titre('Label', $_SERVER["PHP_SELF"], 'p.label', $param, '', '', $sortfield, $sortorder);
530
531 if ($mode == 'future') {
532 print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, $param, '', '', $sortfield, $sortorder, 'right ');
533 print_liste_field_titre('', $_SERVER["PHP_SELF"]);
534 print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ', 'VirtualStockAtDateDesc');
535 print_liste_field_titre('VirtualStock', $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ', 'VirtualStockDesc');
536 } else {
537 print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ');
538 $tooltiptext = $langs->trans("QtyAtDate").' x '.$langs->trans("AverageUnitPricePMPShort").' ('.$langs->trans("Currently").')';
539 print_liste_field_titre("EstimatedStockValue", $_SERVER["PHP_SELF"], "estimatedvalue", '', $param, '', $sortfield, $sortorder, 'right ', $tooltiptext, 1);
540 $tooltiptext = $langs->trans("QtyAtDate").' x '.$langs->trans("SellingPrice").' ('.$langs->trans("Currently").')';
541 print_liste_field_titre("EstimatedStockValueSell", $_SERVER["PHP_SELF"], "", '', $param, '', $sortfield, $sortorder, 'right ', $tooltiptext, 1);
542 $tooltiptext = $langs->trans("MovementsSinceDate");
543 print_liste_field_titre('', $_SERVER["PHP_SELF"], '', '', $param, '', '', '', 'right ', $tooltiptext, 1);
544 print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, $param, '', '', $sortfield, $sortorder, 'right ');
545 }
546
547 // Hook fields
548 $parameters = array('param' => $param, 'sortfield' => $sortfield, 'sortorder' => $sortorder);
549 $reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
550 print $hookmanager->resPrint;
551
552 print_liste_field_titre('', $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ');
553
554 print "</tr>\n";
555}
556
557$totalbuyingprice = 0;
558$totalsellingprice = 0;
559$totalcurrentstock = 0;
560$totalvirtualstock = 0;
561
562$i = 0;
563while ($i < ($limit ? min($num, $limit) : $num)) {
564 $objp = $db->fetch_object($resql);
565
566 if (getDolGlobalString('STOCK_SUPPORTS_SERVICES') || $objp->fk_product_type == 0) {
567 $prod->fetch($objp->rowid);
568
569 // Multilangs
570 /*if (getDolGlobalInt('MAIN_MULTILANGS'))
571 {
572 $sql = 'SELECT label,description';
573 $sql .= ' FROM '.MAIN_DB_PREFIX.'product_lang';
574 $sql .= ' WHERE fk_product = '.((int) $objp->rowid);
575 $sql .= " AND lang = '".$db->escape($langs->getDefaultLang())."'";
576 $sql .= ' LIMIT 1';
577
578 $resqlm = $db->query($sql);
579 if ($resqlm)
580 {
581 $objtp = $db->fetch_object($resqlm);
582 if (!empty($objtp->description)) $objp->description = $objtp->description;
583 if (!empty($objtp->label)) $objp->label = $objtp->label;
584 }
585 }*/
586
587 $currentstock = '';
588 if (!empty($search_fk_warehouse)) {
589 //if ($productid > 0) {
590 foreach ($search_fk_warehouse as $val) {
591 if (!is_numeric($currentstock)) {
592 $currentstock = 0;
593 }
594 $currentstock += empty($stock_prod_warehouse[$objp->rowid][$val]) ? 0 : $stock_prod_warehouse[$objp->rowid][$val];
595 }
596 //} else {
597 // $currentstock = $objp->stock_reel;
598 //}
599 } else {
600 //if ($productid > 0) {
601 $currentstock = empty($stock_prod[$objp->rowid]) ? 0 : $stock_prod[$objp->rowid];
602 //} else {
603 // $currentstock = $objp->stock;
604 //}
605 }
606
607 if ($mode == 'future') {
608 $prod->load_stock('warehouseopen,warehouseinternal,nobatch', 0, $dateendofday);
609 $stock = $prod->stock_theorique; // virtual stock at a date
610 $prod->load_stock('warehouseopen,warehouseinternal,nobatch', 0);
611 $virtualstock = $prod->stock_theorique; // virtual stock in infinite future
612 } else {
613 $stock = $currentstock;
614 $nbofmovement = 0;
615 if (!empty($search_fk_warehouse)) {
616 foreach ($search_fk_warehouse as $val) {
617 $stock -= empty($movements_prod_warehouse[$objp->rowid][$val]) ? 0 : $movements_prod_warehouse[$objp->rowid][$val];
618 $nbofmovement += empty($movements_prod_warehouse_nb[$objp->rowid][$val]) ? 0 : $movements_prod_warehouse_nb[$objp->rowid][$val];
619 }
620 } else {
621 $stock -= empty($movements_prod[$objp->rowid]) ? 0 : $movements_prod[$objp->rowid];
622 $nbofmovement += empty($movements_prod_nb[$objp->rowid]) ? 0 : $movements_prod_nb[$objp->rowid];
623 }
624 }
625
626
627 if ($ext == 'csv') {
628 if ($mode == 'future') {
629 print implode(";", array(
630 '"'.$objp->ref.'"',
631 '"'.$objp->label.'"',
632 '"'.price2num($currentstock, 'MS').'"',
633 '"'.price2num($stock, 'MS').'"',
634 '"'.price2num($virtualstock, 'MS').'"'))."\r\n";
635 $totalvirtualstock += $virtualstock;
636 } else {
637 print implode(";", array(
638 '"'.$objp->ref.'"',
639 '"'.$objp->label.'"',
640 '"'.price(price2num($stock, 'MS')).'"',
641 price2num($stock * $objp->pmp, 'MT') ? '"'.price2num($stock * $objp->pmp, 'MT').'"' : '',
642 !getDolGlobalString('PRODUIT_MULTIPRICES') ? '"'.price2num($stock * $objp->price, 'MT').'"' : '"'.$langs->trans("Variable").'('.$langs->trans("OptionMULTIPRICESIsOn").')"',
643 "$nbofmovement",
644 '"'.price2num($currentstock, 'MS').'"'))."\r\n";
645 $totalbuyingprice += $stock * $objp->pmp;
646 $totalsellingprice += $stock * $objp->price;
647 }
648 $totalcurrentstock += $currentstock;
649 } else {
650 print '<tr class="oddeven">';
651
652 // Product ref
653 print '<td class="nowrap">'.$prod->getNomUrl(1, '').'</td>';
654
655 // Product label
656 print '<td>';
657 print dol_escape_htmltag($objp->label);
658 print '</td>';
659
660 if ($mode == 'future') {
661 // Current stock
662 print '<td class="right">'.price(price2num($currentstock, 'MS')).'</td>';
663 //$totalcurrentstock += $currentstock;
664
665 print '<td class="right"></td>';
666
667 // Virtual stock at date
668 print '<td class="right">'.price(price2num($stock, 'MS')).'</td>';
669
670 // Final virtual stock
671 print '<td class="right">'.price(price2num($virtualstock, 'MS')).'</td>';
672 $totalvirtualstock += $virtualstock;
673 } else {
674 // Stock at date
675 print '<td class="right">'.($stock ? price(price2num($stock, 'MS')) : ('<span class="opacitymedium">0</span>')).'</td>';
676
677 // PMP value
678 $estimatedvalue = $stock * $objp->pmp;
679 print '<td class="right" title="'.dolPrintHTMLForAttribute($langs->trans("AverageUnitPricePMPShort").' ('.$langs->trans("Currently").'): '.price(price2num($objp->pmp, 'MU'), 1)).'">';
680 if (price2num($estimatedvalue, 'MT')) {
681 print '<span class="amount">'.price(price2num($estimatedvalue, 'MT'), 1).'</span>';
682 } else {
683 print '';
684 }
685 $totalbuyingprice += $estimatedvalue;
686 print '</td>';
687
688 // Selling value
689 print '<td class="right"';
690 if (!getDolGlobalString('PRODUIT_MULTIPRICES')) {
691 print ' title="'.dolPrintHTMLForAttribute($langs->trans("SellingPrice").' ('.$langs->trans("Currently").'): '.price(price2num($objp->price, 'MU'), 1));
692 }
693 print '">';
694 if (!getDolGlobalString('PRODUIT_MULTIPRICES')) {
695 print '<span class="amount">';
696 if ($stock || (float) ($stock * $objp->price)) {
697 print price(price2num($stock * $objp->price, 'MT'), 1);
698 }
699 print '</span>';
700 $totalsellingprice += $stock * $objp->price;
701 } else {
702 $htmltext = $langs->trans("OptionMULTIPRICESIsOn");
703 print $form->textwithtooltip('<span class="opacitymedium">'.$langs->trans("Variable").'</span>', $htmltext);
704 }
705 print '</td>';
706
707 // Movements
708 print '<td class="right">';
709 if ($nbofmovement > 0) {
710 $url = DOL_URL_ROOT.'/product/stock/movement_list.php?idproduct='.$objp->rowid;
711 if (GETPOSTISSET('datemonth')) {
712 $url .= '&search_date_startday='.GETPOSTINT('dateday');
713 $url .= '&search_date_startmonth='.GETPOSTINT('datemonth');
714 $url .= '&search_date_startyear='.GETPOSTINT('dateyear');
715 }
716 if (count($search_fk_warehouse) > 1) {
717 $url = ''; // Do not show link, multi warehouse as filter not managed yet by target page
718 } else {
719 foreach ($search_fk_warehouse as $val) {
720 $url .= ($val > 0 ? '&search_warehouse='.((int) $val) : '');
721 }
722 }
723 if ($url) {
724 print '<a href="'.$url.'">';
725 }
726 print $langs->trans("Movements");
727 print '<span class="tabs paddingleft"><span class="badge">'.$nbofmovement.'</span></span>';
728 if ($url) {
729 print '</a>';
730 }
731 }
732 print '</td>';
733
734 // Current stock
735 print '<td class="right">'.($currentstock ? price(price2num($currentstock, 'MS')) : '<span class="opacitymedium">0</span>').'</td>';
736 }
737 $totalcurrentstock += $currentstock;
738
739 // Fields from hook
740 $parameters = array('objp' => $objp);
741 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters); // Note that $action and $object may have been modified by hook
742 print $hookmanager->resPrint;
743
744 // Action
745 print '<td class="right"></td>';
746
747 print '</tr>'."\n";
748 }
749 }
750 $i++;
751}
752
753$parameters = array('sql' => $sql);
754$reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters); // Note that $action and $object may have been modified by hook
755if ($ext != 'csv') {
756 print $hookmanager->resPrint;
757}
758
759$colspan = 8;
760if ($mode == 'future') {
761 $colspan++;
762}
763
764if ($ext == 'csv') {
765 print implode(
766 ";",
767 ($mode == 'future') ? array(
768 '"'.$langs->trans("Totalforthispage").'"',
769 '',
770 $productid > 0 ? price2num($totalcurrentstock, 'MS') : '',
771 '',
772 price(price2num($totalvirtualstock, 'MS'))) :
773 array(
774 '"'.$langs->trans("Totalforthispage").'"',
775 '',
776 '',
777 '"'.price2num($totalbuyingprice, 'MT').'"',
778 !getDolGlobalString('PRODUIT_MULTIPRICES') ? '"'.price2num($totalsellingprice, 'MT').'"' : '',
779 '',
780 $productid > 0 ? price2num($totalcurrentstock, 'MS') : '')
781 );
782} else {
783 if (empty($date) || !$dateIsValid) {
784 print '<tr><td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("EnterADateCriteria").'</span></td></tr>';
785 } else {
786 print '<tr class="liste_total">';
787 print '<td>'.$langs->trans("Totalforthispage").'</td>';
788 print '<td></td>';
789 if ($mode == 'future') {
790 print '<td class="right">'.price(price2num($totalcurrentstock, 'MS')).'</td>';
791 print '<td></td>';
792 print '<td></td>';
793 print '<td class="right">'.price(price2num($totalvirtualstock, 'MS')).'</td>';
794 } else {
795 print '<td></td>';
796 print '<td class="right">'.price(price2num($totalbuyingprice, 'MT')).'</td>';
797 if (!getDolGlobalString('PRODUIT_MULTIPRICES')) {
798 print '<td class="right">'.price(price2num($totalsellingprice, 'MT')).'</td>';
799 } else {
800 print '<td></td>';
801 }
802 print '<td></td>';
803 print '<td class="right">'.($productid > 0 ? price(price2num($totalcurrentstock, 'MS')) : '').'</td>';
804 }
805 print '<td></td>';
806 print '</tr>';
807 }
808
809 print '</table>';
810 print '</div>';
811
812 print dol_get_fiche_end();
813
814 print '</form>';
815
816 llxFooter();
817}
818
819if (!empty($resql)) {
820 $db->free($resql);
821}
822
823$db->close();
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:58
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:70
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.
llxFooter()
Footer empty.
Definition document.php:107
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...
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='')
Load a title with picto.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
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)
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0)
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.
natural_search($fields, $value, $mode=0, $nofirstand=0)
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
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.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
print_liste_field_titre($name, $file="", $field="", $begin="", $moreparam="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $tooltip="", $forcenowrapcolumntitle=0)
Show title line of an array.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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.
restrictedArea(User $user, $features, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $isdraft=0, $mode=0)
Check permissions of a user to show a page and an object.